ナード戦隊データマン

データサイエンスを用いて悪と戦うぞ

Kerasで多入力CNNを構築しsklearnと組合せる

文字ベースのCNNと単語ベースのCNNを組合せ、さらにsklearnでBoW+LogRegのモデルをCNNと組み合わせるコードを書きます。

jupyter notebookで実行

データはこのコンペのものを使います。

In[1]:

import numpy as np 
import pandas as pd 
import os
print(os.listdir("../input"))

Out[1]:

['train.tsv', 'test.tsv', 'sampleSubmission.csv']

データを読み込みます。

In[2]:

df = pd.read_csv("../input/train.tsv", sep="\t")
df_test = pd.read_csv("../input/test.tsv", sep="\t")

必要なモジュールを一気にインポートします。

In[3]:

from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.utils import to_categorical
from sklearn.model_selection import train_test_split
from sklearn.utils import shuffle
from keras.layers import Dense, Input, GlobalMaxPooling1D
from keras.layers import Conv1D, MaxPooling1D, Embedding
from keras.models import Model
from keras.layers import Input, Dense, Embedding, SeparableConv2D, MaxPooling2D, Dropout,concatenate, SpatialDropout1D, SeparableConv1D
from keras.layers.core import Reshape, Flatten
from keras.callbacks import EarlyStopping
from keras.models import Model
from sklearn.metrics import classification_report
from sklearn.linear_model import LogisticRegression
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.pipeline import Pipeline

文字ベースのトークナイザと、単語ベースのトークナイザで前処理します。これにより、CNNへ渡すデータは2つになります。

In[4]:

def preprocess(data, tokenizer, maxlen=290):
    return(pad_sequences(tokenizer.texts_to_sequences(data), maxlen=maxlen))

df = shuffle(df, random_state=42)

maxlen = 300
max_features = 300
maxlen2 = 200
max_features2 = 15000

y = to_categorical(df['Sentiment'])
X = df['Phrase']
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)

tokenizer = Tokenizer(num_words=max_features, filters="", char_level=True)
tokenizer.fit_on_texts(list(X_train))
tokenizer2 = Tokenizer(num_words=max_features2, char_level=False)
tokenizer2.fit_on_texts(list(X_train))

X_train1 = preprocess(X_train, tokenizer, maxlen)
X_test1 = preprocess(X_test, tokenizer, maxlen)
X_train2 = preprocess(X_train, tokenizer2, maxlen2)
X_test2 = preprocess(X_test, tokenizer2, maxlen2)
testdata = preprocess(df_test['Phrase'], tokenizer, maxlen)
testdata2 = preprocess(df_test['Phrase'], tokenizer2, maxlen2)

assert X_train1.shape[0] == X_train2.shape[0]
assert X_test1.shape[0] == X_test2.shape[0]

2つの入力をそれぞれ別のCNNへ渡し、出力層の前で結合します。

In[5]:

num_labels = 5
sequence_length = maxlen2
filter_sizes = [3,4,5]
num_filters = 100
drop = 0.5
dim1 = 150
dim2 = 200

inputs = Input(shape=(maxlen,))
inputs2 = Input(shape=(maxlen2, ))
embedding = Embedding(max_features, dim1, input_length=maxlen)(inputs)
tmp_drop = SpatialDropout1D(0.2)(embedding)
tmp_conv = SeparableConv1D(32, kernel_size=3, padding='same', activation='relu')(tmp_drop)
tmp_pool = MaxPooling1D(pool_size=2)(tmp_conv)
tmp_conv = SeparableConv1D(64, kernel_size=3, padding='same', activation='relu')(tmp_pool)
tmp_pool = MaxPooling1D(pool_size=2)(tmp_conv)
out1 = Flatten()(tmp_pool)

embedding2 = Embedding(max_features2, dim2, input_length=maxlen2)(inputs2)
tmp_drop = SpatialDropout1D(0.2)(embedding2)
tmp_conv = SeparableConv1D(32, kernel_size=3, padding='same', activation='relu')(tmp_drop)
tmp_pool = MaxPooling1D(pool_size=2)(tmp_conv)
tmp_conv = SeparableConv1D(64, kernel_size=3, padding='same', activation='relu')(tmp_pool)
tmp_pool = MaxPooling1D(pool_size=2)(tmp_conv)
out2 = Flatten()(tmp_pool)
out = concatenate([out1, out2])
output = Dense(units=num_labels, activation='softmax')(out)

model = Model([inputs, inputs2], output)

model.compile(loss='categorical_crossentropy', optimizer="rmsprop", metrics=['acc'])

callbacks = [EarlyStopping(monitor='val_loss')]

CNNモデルを訓練します。

In[6]:

epochs = 30
model.fit([X_train1, X_train2], y_train, batch_size=1000, epochs=epochs, verbose=1, validation_data=([X_test1, X_test2], y_test), callbacks=callbacks)

つぎに、BoW + LogRegモデルを訓練します。

In[7]:

pipe = Pipeline([
    ('vect', CountVectorizer(stop_words="english", ngram_range=(1,2), max_features=20000)),
    ('clf', LogisticRegression())
])

pipe.fit(df['Phrase'], df['Sentiment'])

最後に、目的のデータで予測結果を出し、その結果の平均をとります。

In[8]:

y_preds_skl = pipe.predict_proba(df_test['Phrase'])
y_preds_ker = model.predict([testdata, testdata2])
y_preds = (y_preds_skl + y_preds_ker)/2.0
df_test['Sentiment'] = np.argmax(y_preds, axis=1)
df_test[['PhraseId', 'Sentiment']].to_csv("result.csv", index=False)

説明

まず、他入力のCNNでは、文字ベース入力と単語ベース入力を分けました。これらの入力は配列で渡されます。出力層までのどこかで2つのCNNを結合するだけです。

次に、ディープラーニングとsklearnのアンサンブルモデルを作成するために、予測結果の平均をとりました。このような方法は、結構一般的です。

で、予測精度については、そこそこありますが、tensorflow-hubのnnlmとtfidfを結合したlogregモデルのほうが精度は高かったので、kerasを使う意味とは...という感じです。

結局、シンプルでより高い精度を出すケースが多いので、複雑なモデリングが必要になるケースは限定されます。どんなネットワークが良いのかは実際に試してみるしか確かめる方法がないので、その試行錯誤の一環としてこういう方法がある、ということです。

ネットワークやハイパーパラメータの組合せは膨大なので、AutoMLのようなツールを使ったほうが良いかもしれません。