Python for NLPの連載は今回で18回目です。
前回の記事では、Python の Keras ライブラリーを使用して、深層学習ベースの映画センチメント分析モデルを作成する方法を説明しました。
その記事では、IMDB上のさまざまな映画に関するユーザーレビューのセンチメント分析を実行する方法を見ました。
私たちは、センチメントを予測するためにレビューのテキストを使用しました。
しかし、テキスト分類のタスクでは、テキストを分類するためにテキスト以外の情報を利用することもできます。
例えば、性別はレビューのセンチメントに影響を与える可能性がある。
さらに、国籍は特定の映画についての世論に影響を与えるかもしれません。
したがって、メタデータとしても知られるこの関連情報は、統計モデルの精度を向上させるために使用することができます。
この記事では、前2回の記事で学んだ概念を基に、異なるビジネスに関するユーザーレビューを、「良い」、「悪い」、「普通」の3つの定義済みカテゴリのいずれかに分類するテキスト分類システムの作成方法について説明します。
しかし、レビューのテキストに加えて、レビューの関連するメタデータを使用して分類を実行します。
テキスト入力と数値入力という2種類の入力があるため、複数入力モデルを作成する必要があります。
Keras Functional APIは複数入力、複数出力のモデルをサポートしているので、これを使用することにします。
この記事を読むと、複数の入力を受け入れ、2つの出力を連結し、集約された入力を使用して分類または回帰を実行することができるKerasの深層学習モデルを作成することができるようになります。
- データセット
- テキスト入力のみのモデル作成
- メタ情報のみを入力とするモデルの作成
- 複数の入力を持つモデルの作成
- 最終的な感想と改善点
このようなモデルを作成する詳細に入る前に、まず、これから使うデータセットを簡単に復習しておきましょう。
データセット
この記事のデータセットは、このKaggleのリンクからダウンロードすることができます。
このデータセットには複数のファイルが含まれていますが、私たちは yelp_review.csv
ファイルにのみ興味を持っています。
このファイルには、レストラン、バー、歯医者、医者、美容院など、様々なビジネスに関する520万以上のレビューが含まれています。
今回は、最初の50,000件のみを使用してモデルを学習します。
データセットをローカルマシンにダウンロードします。
データセットをインポートする前に、まずこの記事で使用するすべてのライブラリをインポートしましょう。
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
まず、データセットを読み込みます。
以下のスクリプトがそれを行う。
yelp_reviews = pd.read_csv("/content/drive/My Drive/yelp_review_short.csv")
このデータセットには Stars
というカラムがあり、さまざまなビジネスに対する評価が格納されている。
Stars」列は1から5までの値を持つことができる。
ここでは、レビューの数値をカテゴリーに変換することで問題を単純化する。
データセットに新しいカラム reviews_score
を追加する。
もしユーザーレビューの Stars
カラムの値が 1 であれば、 reviews_score
カラムには bad
という文字列が入ります。
もし Stars
カラムのレーティングが 2 か 3 ならば、 reviews_score
カラムには average
という値が格納されます。
最後にレビューの評価が 4 または 5 の場合、 reviews_score
カラムには good
という対応する値が格納されます。
以下のスクリプトはこの前処理を行うものです。
bins = [0,1,3,5]
review_names = ['bad', 'average', 'good']
yelp_reviews['reviews_score'] = pd.cut(yelp_reviews['stars'], bins, labels=review_names)
次に、データフレームからNULL値をすべて取り除き、データセットの形とヘッダを表示します。
yelp_reviews.isnull().values.any()
print(yelp_reviews.shape)
yelp_reviews.head()
出力には (50000,10)
と表示されます。
これは、このデータセットには50,000件のレコードと10個のカラムがあることを意味します。
yelp_reviews` データフレームのヘッダは、以下のようになります。
新しく追加された reviews_score
カラムを含め、データフレームに含まれる 10 カラムを確認することができます。
textカラムにはレビューのテキストが、
usefulカラムにはそのレビューが役に立った人の数を表す数値が格納されています。
同様に、funnyと
coolカラムには、それぞれ
funnyと
cool` と評価した人の数が格納されています。
ランダムにレビューを選んでみましょう。
4番目のレビュー(インデックス3)を見ると、星が4つあるので、good
と表示されています。
このレビューの全文を見てみましょう。
print(yelp_reviews["text"][3])
出力はこのようになります。
Love coming here. Yes the place always needs the floor swept but when you give out peanuts in the shell how won't it always be a bit dirty.
The food speaks for itself, so good. Burgers are made to order and the meat is put on the grill when you order your sandwich. Getting the small burger just means 1 patty, the regular is a 2 patty burger which is twice the deliciousness.
Getting the Cajun fries adds a bit of spice to them and whatever size you order they always throw more fries (a lot more fries) into the bag.
これはポジティブなレビューであることがはっきりわかります。
次に、「良い」レビュー、「平均的」レビュー、「悪い」レビューの数をプロットしてみましょう。
import seaborn as sns
sns.countplot(x='reviews_score', data=yelp_reviews)
上のプロットから明らかなように、レビューの大部分は良いレビューであり、次に平均的なレビューが続いています。
ネガティブなレビューの数は非常に少ないです。
我々はデータの前処理を行い、この記事では3つのモデルを作成します。
最初のモデルは、レビューが「良い」、「平均的」、「悪い」のどれであるかを予測するためにテキスト入力だけを使うことにする。
2つ目のモデルでは、テキストは使いません。
我々はレビューのセンチメントを予測するためにuseful
、funny
、cool
などのメタ情報のみを使用することにする。
最後に、テキスト分類のために、テキストとメタ情報という複数の入力を受け付けるモデルを作成する。
テキスト入力のみのモデルの作成
まず最初に、テキストデータをクリーニングする関数を定義します。
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
このモデルではテキストのみを使用するので、すべてのテキストレビューをフィルタリングしてリストに保存します。
テキストレビューは preprocess_text
関数を用いてクリーニングされます。
この関数はテキストから句読点と数字を削除します。
X = []
sentences = list(yelp_reviews["text"])
for sen in sentences:
X.append(preprocess_text(sen))
y = yelp_reviews['reviews_score']
X変数にはテキストレビューが格納され、
y変数には対応する
reviews_score値が格納されます。
reviews_score カラムには、テキスト形式のデータが格納されています。
テキストを一発符号化されたベクトルに変換する必要があります。
keras.utilsモジュールの
to_categoricalメソッドを使用することができます。
しかし、まずはsklearn.preprocessingモジュールの
LabelEncoder` 関数を使って、テキストを整数のラベルに変換する必要があります。
from sklearn import preprocessing
# label_encoder object knows how to understand word labels.
label_encoder = preprocessing.LabelEncoder()
# Encode labels in column 'species'.
y = label_encoder.fit_transform(y)
それでは、データをテストセットとトレーニングセットに分割してみましょう。
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=42)
ここで、学習用とテスト用のラベルを一発符号化されたベクトルに変換することができます。
from keras.utils import to_categorical
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)
単語埋め込みの記事で、機械学習や深層学習モデルのような統計的アルゴリズムで利用する前に、テキストデータをある種の数値形式に変換する必要があることを説明しました。
テキストを数値に変換する方法の1つが、ワードエンベッディングです。
もしあなたがKerasによるワードエンベッディングの実装方法を知らないなら、コードの次のセクションに進む前に、この記事を読むことを強くお勧めします。
単語埋め込みの最初のステップは、単語をそれに対応する数値インデックスに変換することです。
そのためには、 Keras.preprocessing.text
モジュールの Tokenizer
クラスを使用します。
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)
文は様々な長さを持つので、Tokenizer
クラスが返すシーケンスも様々な長さを持つことになります。
ここでは、シーケンスの最大長が 200 になるように指定している (ただし、任意の数を試すことができる)。
200 未満の長さの文では、残りのインデックスに 0 が埋められる。
200より長い文章は、残りのインデックスが切り捨てられる。
次のスクリプトを見てほしい。
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()
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()
最後に、埋め込み行列を作成します。
行は語彙の数(+1)に等しくなります。
GloVeの単語埋め込みは100次元のベクトルで表現されるので、列数は100となります。
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
単語の埋め込みが完了したら、いよいよモデルの作成に入ります。
今回はKerasの関数型APIを使ってモデルを作成します。
今回作成するような単一入力モデルは逐次APIでも作成できますが、次章ではKerasの関数型APIでしか作成できない多入力モデルを作成しますので、本節でも関数型APIを使用します。
入力層(埋め込み層)1層、ニューロン128個のLSTM層1層、出力層としても機能する密な層1層という非常にシンプルなモデルを作成します。
出力は3つあるので、ニューロン数は3、活性化関数はsoftmax
とします。
損失関数として categorical_crossentropy
を、最適化関数として adam
を用いる。
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(3, activation='softmax')(LSTM_Layer_1)
model = Model(inputs=deep_inputs, outputs=dense_layer_1)
model.compile(loss='categorical_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) 5572900
_________________________________________________________________
lstm_1 (LSTM) (None, 128) 117248
_________________________________________________________________
dense_1 (Dense) (None, 3) 387
=================================================================
Total params: 5,690,535
Trainable params: 117,635
Non-trainable params: 5,572,900
最後に、ニューラルネットワークのブロック図を表示します。
from keras.utils import plot_model
plot_model(model, to_file='model_plot1.png', show_shapes=True, show_layer_names=True)
ローカルのファイルパスに model_plot1.png
というファイルが作成されます。
この画像を開くと次のように表示されます。
このモデルは1つの入力層、1つの埋め込み層、1つのLSTM、そして出力層として機能する1つの密な層から構成されていることがわかる。
では、このモデルを学習させてみましょう。
history = model.fit(X_train, y_train, batch_size=128, epochs=10, verbose=1, validation_split=0.2)
このモデルは80%の訓練データで学習され、20%の訓練データで検証されます。
10エポックの結果は以下の通りです。
Train on 32000 samples, validate on 8000 samples
Epoch 1/10
32000/32000 [==============================] - 81s 3ms/step - loss: 0.8640 - acc: 0.6623 - val_loss: 0.8356 - val_acc: 0.6730
Epoch 2/10
32000/32000 [==============================] - 80s 3ms/step - loss: 0.8508 - acc: 0.6618 - val_loss: 0.8399 - val_acc: 0.6690
Epoch 3/10
32000/32000 [==============================] - 84s 3ms/step - loss: 0.8461 - acc: 0.6647 - val_loss: 0.8374 - val_acc: 0.6726
Epoch 4/10
32000/32000 [==============================] - 82s 3ms/step - loss: 0.8288 - acc: 0.6709 - val_loss: 0.7392 - val_acc: 0.6861
Epoch 5/10
32000/32000 [==============================] - 82s 3ms/step - loss: 0.7444 - acc: 0.6804 - val_loss: 0.6371 - val_acc: 0.7311
Epoch 6/10
32000/32000 [==============================] - 83s 3ms/step - loss: 0.5969 - acc: 0.7484 - val_loss: 0.5602 - val_acc: 0.7682
Epoch 7/10
32000/32000 [==============================] - 82s 3ms/step - loss: 0.5484 - acc: 0.7623 - val_loss: 0.5244 - val_acc: 0.7814
Epoch 8/10
32000/32000 [==============================] - 86s 3ms/step - loss: 0.5052 - acc: 0.7866 - val_loss: 0.4971 - val_acc: 0.7950
Epoch 9/10
32000/32000 [==============================] - 84s 3ms/step - loss: 0.4753 - acc: 0.8032 - val_loss: 0.4839 - val_acc: 0.7965
Epoch 10/10
32000/32000 [==============================] - 82s 3ms/step - loss: 0.4539 - acc: 0.8110 - val_loss: 0.4622 - val_acc: 0.8046
モデルの最終的な学習精度は81.10%であり、検証精度は80.46であることがわかります。
この差は非常に小さいので、このモデルは学習データに対してオーバーフィットしていないと考えられます。
それでは、テストセットでモデルの性能を評価しましょう。
score = model.evaluate(X_test, y_test, verbose=1)
print("Test Score:", score[0])
print("Test Accuracy:", score[1])
出力は次のようになります。
10000/10000 [==============================] - 37s 4ms/step
Test Score: 0.4592904740810394
Test Accuracy: 0.8101
最後に、トレーニングセットとテストセットの損失と精度の値をプロットしましょう。
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()
以下の2つのプロットが表示されるはずです。
トレーニングセットとテストセットの精度、損失は互いに近い値になっており、モデルがオーバーフィットしていないことがわかります。
メタ情報のみのモデルを作成する
このセクションでは、yelp のレビューの useful
, funny
, cool
カラムの情報を使って分類モデルを作成します。
これらの列のデータはよく構造化されており、連続的あるいは空間的なパターンを含んでいないので、予測を行うために単純な密結合ニューラルネットワークを使うことができる。
レビューのスコアに対して、「役に立つ」、「面白い」、「かっこいい」レビューの平均カウントをプロットしてみましょう。
import seaborn as sns
sns.barplot(x='reviews_score', y='useful', data=yelp_reviews)
出力から、useful
とマークされたレビューの平均カウントは悪いレビューで最も高く、平均的なレビューと良いレビューがそれに続いていることがわかります。
次に、面白い
レビューの平均カウントをプロットしてみましょう。
sns.barplot(x='reviews_score', y='funny', data=yelp_reviews)
出力は、再び、funny
とマークされたレビューの平均カウントが悪いレビューのために最も高いことを示しています。
最後に、reviews_score
カラムに対してcool
カラムの平均値をプロットしてみましょう。
人々はポジティブまたは良いレビューをクールとしてマークすることが多いので、cool
カラムの平均カウントは良いレビューで最も高くなることが予想されます。
sns.barplot(x='reviews_score', y='cool', data=yelp_reviews)
予想通り、良いレビューの平均クールカウントは最も高いです。
この情報から、useful
, funny
, cool
カラムのカウント値は reviews_score
カラムと何らかの相関があると仮定してよいでしょう。
そこで、この3つのカラムのデータを使って、 reviews_score
カラムの値を予測するアルゴリズムを学習してみることにします。
それでは、purデータセットからこれらの3つのカラムをフィルタリングしてみましょう。
yelp_reviews_meta = yelp_reviews[['useful', 'funny', 'cool']]
X = yelp_reviews_meta.values
y = yelp_reviews['reviews_score']
次に、ラベルを一発符号化した値に変換し、データを訓練セットとテストセットに分割します。
from sklearn import preprocessing
# label_encoder object knows how to understand word labels.
label_encoder = preprocessing.LabelEncoder()
# Encode labels in column 'species'.
y = label_encoder.fit_transform(y)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=42)
from keras.utils import to_categorical
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)
次のステップは、モデルの作成である。
入力層、10ニューロンとrelu活性化関数を持つ2つの密な隠れ層、そして最後に3ニューロンとソフトマックス活性化関数を持つ出力密な層である。
損失関数とオプティマイザは、それぞれ categorical_crossentropy
と adam
とする。
以下のスクリプトでモデルを定義する。
input2 = Input(shape=(3,))
dense_layer_1 = Dense(10, activation='relu')(input2)
dense_layer_2 = Dense(10, activation='relu')(dense_layer_1)
output = Dense(3, activation='softmax')(dense_layer_2)
model = Model(inputs=input2, outputs=output)
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])
モデルの概要を出力してみよう。
print(model.summary())
_________________________________________________________________
Layer (type) Output Shape Param #=================================================================
input_1 (InputLayer) (None, 3) 0
_________________________________________________________________
dense_1 (Dense) (None, 10) 40
_________________________________________________________________
dense_2 (Dense) (None, 10) 110
_________________________________________________________________
dense_3 (Dense) (None, 3) 33
=================================================================
Total params: 183
Trainable params: 183
Non-trainable params: 0
最後に、このモデルのブロック図は次のスクリプトで作成できます。
from keras.utils import plot_model
plot_model(model, to_file='model_plot2.png', show_shapes=True, show_layer_names=True)
さて、ローカルのファイルパスから model_plot2.png
ファイルを開くと、次のようになります。
それでは、モデルを学習させ、各エポックの精度と損失値を表示してみましょう。
history = model.fit(X_train, y_train, batch_size=16, epochs=10, verbose=1, validation_split=0.2)
Train on 32000 samples, validate on 8000 samples
Epoch 1/10
32000/32000 [==============================] - 8s 260us/step - loss: 0.8429 - acc: 0.6649 - val_loss: 0.8166 - val_acc: 0.6734
Epoch 2/10
32000/32000 [==============================] - 7s 214us/step - loss: 0.8203 - acc: 0.6685 - val_loss: 0.8156 - val_acc: 0.6737
Epoch 3/10
32000/32000 [==============================] - 7s 217us/step - loss: 0.8187 - acc: 0.6685 - val_loss: 0.8150 - val_acc: 0.6736
Epoch 4/10
32000/32000 [==============================] - 7s 220us/step - loss: 0.8183 - acc: 0.6695 - val_loss: 0.8160 - val_acc: 0.6740
Epoch 5/10
32000/32000 [==============================] - 7s 227us/step - loss: 0.8177 - acc: 0.6686 - val_loss: 0.8149 - val_acc: 0.6751
Epoch 6/10
32000/32000 [==============================] - 7s 219us/step - loss: 0.8175 - acc: 0.6686 - val_loss: 0.8157 - val_acc: 0.6744
Epoch 7/10
32000/32000 [==============================] - 7s 216us/step - loss: 0.8172 - acc: 0.6696 - val_loss: 0.8145 - val_acc: 0.6733
Epoch 8/10
32000/32000 [==============================] - 7s 214us/step - loss: 0.8175 - acc: 0.6689 - val_loss: 0.8139 - val_acc: 0.6734
Epoch 9/10
32000/32000 [==============================] - 7s 215us/step - loss: 0.8169 - acc: 0.6691 - val_loss: 0.8160 - val_acc: 0.6744
Epoch 10/10
32000/32000 [==============================] - 7s 216us/step - loss: 0.8167 - acc: 0.6694 - val_loss: 0.8138 - val_acc: 0.6736
出力から、このモデルは収束しておらず、精度値はすべてのエポックにおいて66と67の間に留まっていることがわかります。
テストセットでモデルがどのように実行されるか見てみましょう。
score = model.evaluate(X_test, y_test, verbose=1)
print("Test Score:", score[0])
print("Test Accuracy:", score[1])
10000/10000 [==============================] - 0s 34us/step
Test Score: 0.8206425309181213
Test Accuracy: 0.6669
以下のスクリプトで、トレーニングセットとテストセットの損失と精度の値を表示することができます。
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()
出力から、精度の値が相対的に低いことがわかります。
したがって、このモデルはアンダーフィットであると言えます。
精度は、密な層の数を増やすか、エポック数を増やすことで向上させることができますが、それはあなたにお任せします。
この記事の最後の、そして最も重要なセクションに移りましょう。
ここでは、異なる種類の複数の入力を使ってモデルを学習させます。
複数の入力を持つモデルの作成
これまでの節で、テキストデータかメタ情報を使って深層学習モデルを学習させる方法を見ました。
では、テキスト情報とメタ情報を組み合わせて、モデルの入力として使いたい場合はどうすればよいのでしょうか。
Kerasの関数型APIを使えば、それが可能になります。
このセクションでは、2つのサブモデルを作成します。
最初のサブモデルは、テキストレビューという形でテキスト入力を受け付けます。
このサブモデルは、入力形状層、埋め込み層、128ニューロンのLSTM層で構成されます。
第二のサブモデルはuseful
、funny
、cool
のカラムからメタ情報の形で入力を受け入れる。
2 番目のサブモデルも 3 つの層から構成される。
入力層と二つの密な層である。
最初のサブモデルの LSTM 層からの出力と、2 番目のサブモデルの密な層からの出力は一緒に連結され、10 個のニューロンを持つ別の密な層への連結入力として使われる。
最後に、出力密な層は、各レビュータイプに対応する3つのニューロンを持つことになります。
このような連結モデルをどのように作成するか見てみましょう。
まず、2つの異なるタイプの入力を作成する必要がある。
そのために、以下のように、データを特徴セットとラベルセットに分ける。
X = yelp_reviews.drop('reviews_score', axis=1)
y = yelp_reviews['reviews_score']
X変数は特徴セットを含み、
y変数はラベルセットを含む。
ラベルは一発符号化されたベクトルに変換する必要がある。
これは、ラベルエンコーダとkeras.utilsモジュールの
to_categorical` 関数を用いて行うことができます。
また、データを学習セットと特徴セットに分割する。
from sklearn import preprocessing
# label_encoder object knows how to understand word labels.
label_encoder = preprocessing.LabelEncoder()
# Encode labels in column 'species'.
y = label_encoder.fit_transform(y)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=42)
from keras.utils import to_categorical
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)
これでラベルセットは必要な形式になりました。
出力は1つだけなので、ラベルセットを処理する必要はありません。
しかし、モデルには複数の入力があります。
そこで、特徴セットを前処理する必要がある。
まず、データセットの前処理に使う preproces_text
関数を作成しましょう。
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
まず最初に、トレーニングセットとテストセットのテキスト入力を作成します。
以下のスクリプトを見てください。
X1_train = []
sentences = list(X_train["text"])
for sen in sentences:
X1_train.append(preprocess_text(sen))
X1_train` にはトレーニングセットのテキスト入力が格納される。
同様に、以下のスクリプトはテスト集合のテキスト入力データを前処理する。
X1_test = []
sentences = list(X_test["text"])
for sen in sentences:
X1_test.append(preprocess_text(sen))
次に、訓練セットとテストセットのテキスト入力を、単語埋め込みを用いて数値に変換する必要がある。
以下のスクリプトがそれを行う。
tokenizer = Tokenizer(num_words=5000)
tokenizer.fit_on_texts(X1_train)
X1_train = tokenizer.texts_to_sequences(X1_train)
X1_test = tokenizer.texts_to_sequences(X1_test)
vocab_size = len(tokenizer.word_index) + 1
maxlen = 200
X1_train = pad_sequences(X1_train, padding='post', maxlen=maxlen)
X1_test = pad_sequences(X1_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/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
テキスト入力の前処理を行いました。
2つ目の入力は、useful
, funny
, cool
というカラムに含まれるメタ情報である。
これらのカラムを特徴量セットからフィルタリングして、アルゴリズムを学習するためのメタ入力を作成する。
以下のスクリプトを見てみよう。
X2_train = X_train[['useful', 'funny', 'cool']].values
X2_test = X_test[['useful', 'funny', 'cool']].values
それでは2つの入力レイヤーを作成しよう。
最初の入力層はテキストを入力するのに使い、2番目の入力層は3つのカラムからメタ情報を入力するのに使う。
input_1 = Input(shape=(maxlen,))
input_2 = Input(shape=(3,))
最初の入力レイヤー input_1
がテキスト入力に使われるのがわかるでしょう。
形状の大きさは入力文の形状に設定されている。
2番目の入力層では、シェイプは3つのカラムに対応する。
それでは、第一入力層からデータを受け取る最初のサブモデルを作成しよう。
embedding_layer = Embedding(vocab_size, 100, weights=[embedding_matrix], trainable=False)(input_1)
LSTM_Layer_1 = LSTM(128)(embedding_layer)
同様に、次のスクリプトは第二入力層からの入力を受け付ける第二サブモデルを作成する。
dense_layer_1 = Dense(10, activation='relu')(input_2)
dense_layer_2 = Dense(10, activation='relu')(dense_layer_1)
これで2つのサブモデルができた。
ここでやりたいことは、最初のサブモデルからの出力と2番目のサブモデルからの出力を連結することである。
最初のサブモデルの出力は LSTM_Layer_1
の出力であり、同様に、2番目のサブモデルの出力は dense_layer_2
の出力である。
2つの入力を連結するために、 keras.layers.merge
モジュールの Concatenate
クラスを使用することができます。
次のスクリプトは最終的なモデルを作成するものです。
concat_layer = Concatenate()([LSTM_Layer_1, dense_layer_2])
dense_layer_3 = Dense(10, activation='relu')(concat_layer)
output = Dense(3, activation='softmax')(dense_layer_3)
model = Model(inputs=[input_1, input_2], outputs=output)
このモデルは2つのアイテムを持つ入力リストを持っていることがわかります。
次のスクリプトはモデルをコンパイルし、そのサマリーを表示します。
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])
print(model.summary())
モデルの要約は次のとおりである。
Layer (type) Output Shape Param # Connected to
==================================================================================================
input_1 (InputLayer) (None, 200) 0
__________________________________________________________________________________________________
input_2 (InputLayer) (None, 3) 0
__________________________________________________________________________________________________
embedding_1 (Embedding) (None, 200, 100) 5572900 input_1[0][0]
__________________________________________________________________________________________________
dense_1 (Dense) (None, 10) 40 input_2[0][0]
__________________________________________________________________________________________________
lstm_1 (LSTM) (None, 128) 117248 embedding_1[0][0]
__________________________________________________________________________________________________
dense_2 (Dense) (None, 10) 110 dense_1[0][0]
__________________________________________________________________________________________________
concatenate_1 (Concatenate) (None, 138) 0 lstm_1[0][0]
dense_2[0][0]
__________________________________________________________________________________________________
dense_3 (Dense) (None, 10) 1390 concatenate_1[0][0]
__________________________________________________________________________________________________
dense_4 (Dense) (None, 3) 33 dense_3[0][0]
==================================================================================================
Total params: 5,691,721
Trainable params: 118,821
Non-trainable params: 5,572,900
最後に、次のスクリプトを使って完全なネットワークモデルをプロットします。
from keras.utils import plot_model
plot_model(model, to_file='model_plot3.png', show_shapes=True, show_layer_names=True)
model_plot3.png`ファイルを開くと、以下のようなネットワーク図が表示されます。
上の図は、複数の入力を1つの入力に連結してモデルを作成したことを明確に説明しています。
それでは、このモデルを学習させ、その結果を見てみましょう。
history = model.fit(x=[X1_train, X2_train], y=y_train, batch_size=128, epochs=10, verbose=1, validation_split=0.2)
10エポックの結果がこちらです。
Train on 32000 samples, validate on 8000 samples
Epoch 1/10
32000/32000 [==============================] - 155s 5ms/step - loss: 0.9006 - acc: 0.6509 - val_loss: 0.8233 - val_acc: 0.6704
Epoch 2/10
32000/32000 [==============================] - 154s 5ms/step - loss: 0.8212 - acc: 0.6670 - val_loss: 0.8141 - val_acc: 0.6745
Epoch 3/10
32000/32000 [==============================] - 154s 5ms/step - loss: 0.8151 - acc: 0.6691 - val_loss: 0.8086 - val_acc: 0.6740
Epoch 4/10
32000/32000 [==============================] - 155s 5ms/step - loss: 0.8121 - acc: 0.6701 - val_loss: 0.8039 - val_acc: 0.6776
Epoch 5/10
32000/32000 [==============================] - 154s 5ms/step - loss: 0.8027 - acc: 0.6740 - val_loss: 0.7467 - val_acc: 0.6854
Epoch 6/10
32000/32000 [==============================] - 155s 5ms/step - loss: 0.6791 - acc: 0.7158 - val_loss: 0.5764 - val_acc: 0.7560
Epoch 7/10
32000/32000 [==============================] - 154s 5ms/step - loss: 0.5333 - acc: 0.7744 - val_loss: 0.5076 - val_acc: 0.7881
Epoch 8/10
32000/32000 [==============================] - 154s 5ms/step - loss: 0.4857 - acc: 0.7973 - val_loss: 0.4849 - val_acc: 0.7970
Epoch 9/10
32000/32000 [==============================] - 154s 5ms/step - loss: 0.4697 - acc: 0.8034 - val_loss: 0.4709 - val_acc: 0.8024
Epoch 10/10
32000/32000 [==============================] - 154s 5ms/step - loss: 0.4479 - acc: 0.8123 - val_loss: 0.4592 - val_acc: 0.8079
モデルを評価するために、以下のように evaluate
関数に両方のテスト入力を渡す必要があります。
score = model.evaluate(x=[X1_test, X2_test], y=y_test, verbose=1)
print("Test Score:", score[0])
print("Test Accuracy:", score[1])
結果は以下の通りです。
10000/10000 [==============================] - 18s 2ms/step
Test Score: 0.4576087875843048
Test Accuracy: 0.8053
テスト精度は80.53%であり、テキスト入力のみを用いた最初のモデルより若干低くなっています。
これは yelp_reviews
のメタ情報は感情予測にあまり有用でないことを示しています。
とにかく、これでKerasでテキスト分類のための複数入力モデルを作る方法がわかりましたね。
最後に、トレーニングセットとテストセットの損失と精度を出力してみましょう。
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()
損失と精度の値は、トレーニングセットとテストセットの間で最小の差であることがわかります。
最終的な感想と改善点
この記事の目的は、異なる種類の複数の入力を受け入れる深層学習モデルの作成方法を説明することであるため、この記事では非常に単純なニューラルネットワークを構築しました。
以下は、テキスト分類モデルの性能をさらに向上させるためのヒントです。
- この記事では、ハードウェアの制約があったため、520万レコードのうち5万レコードのみを使用しました。より多くのレコードでモデルを学習させ、より良いパフォーマンスを達成できるかどうかを確認することができます。
-
- モデルへのLSTMや密な層の追加を試してみる。もしモデルがオーバーフィットするようであれば、ドロップアウトを追加してみる。
-
- オプティマイザー関数を変更し、より多くのエポック数でモデルを学習させてみる。
あなたの結果を、ニューラルネットワークの構成とともに、コメント欄で共有してください。
どの程度うまくいったか、ぜひ拝見させてください。