간단한 LSTM + embedding과 샘플가중치 및 여러개의 결과를 출력하는 모델

simple_LSTM

Simple LSTM과 Embedding

  • 이번에는 결과를 두개 동시에 예측하는 방법입니다.
In [0]:
import numpy as np
import pandas as pd
from keras.models import Model
from keras.layers import Input, Dense, Embedding, SpatialDropout1D, add, concatenate
from keras.layers import CuDNNLSTM, Bidirectional, GlobalMaxPooling1D, GlobalAveragePooling1D
from keras.preprocessing import text, sequence
from keras.callbacks import LearningRateScheduler

# 미리 훈련시켜놓은 glove, crawl을 불러온다.
EMBEDDING_FILES = [
    './input/crawl-300d-2M.vec',
    './input/glove.840B.300d.txt'
]

# 몇개의 모델을 만들어서 앙상블 할것인가.
NUM_MODELS = 2

# 몇개의 배치사이즈를 통해 훈련을 할것인가 결정
BATCH_SIZE = 512

LSTM_UNITS = 128
DENSE_HIDDEN_UNITS = 4 * LSTM_UNITS
EPOCHS = 2

# 최대 분석대상이 되는 문장 길이
MAX_LEN = 220
IDENTITY_COLUMNS = [
    'male', 'female', 'homosexual_gay_or_lesbian', 'christian', 'jewish',
    'muslim', 'black', 'white', 'psychiatric_or_mental_illness'
]

AUX_COLUMNS = ['target', 'severe_toxicity', 'obscene', 'identity_attack', 'insult', 'threat']
TEXT_COLUMN = 'comment_text'
TARGET_COLUMN = 'target'
CHARS_TO_REMOVE = '!"#$%&()*+,-./:;<=>?@[\\]^_`{|}~\t\n“”’\'∞θ÷α•à−β∅³π‘₹´°£€\×™√²—'
In [ ]:
def get_coefs(word, *arr):
    return word, np.asarray(arr, dtype='float32')
In [ ]:
def load_embeddings(path):
    with open(path) as f:
        return dict(get_coefs(*line.strip().split(' ')) for line in f)
  • embedding으로부터 실제 단어를 임베딩으로 교체한다.
In [ ]:
def build_matrix(word_index, path):
    embedding_index = load_embeddings(path)
    embedding_matrix = np.zeros((len(word_index) + 1, 300))
    for word, i in word_index.items():
        try:
            embedding_matrix[i] = embedding_index[word]
        except KeyError:
            pass
    return embedding_matrix
    
  • SpatialDropout1D는 특정 컬럼을 전체를 떨구는것으로써,
  • 텍스트분석등은 대충 떨궈놓으면 어차피 주위 단어로 유추가 전부 가능하기 때문에
  • 드랍아웃을 하는 의의가 없어지기 때문에 한줄을 죄다 없애버린다.
In [0]:
    

def build_model(embedding_matrix, num_aux_targets):
    words = Input(shape=(None,))
    x = Embedding(*embedding_matrix.shape, weights=[embedding_matrix], trainable=False)(words)
    x = SpatialDropout1D(0.2)(x)
    x = Bidirectional(CuDNNLSTM(LSTM_UNITS, return_sequences=True))(x)
    x = Bidirectional(CuDNNLSTM(LSTM_UNITS, return_sequences=True))(x)

    hidden = concatenate([
        GlobalMaxPooling1D()(x),
        GlobalAveragePooling1D()(x),
    ])
    hidden = add([hidden, Dense(DENSE_HIDDEN_UNITS, activation='relu')(hidden)])
    hidden = add([hidden, Dense(DENSE_HIDDEN_UNITS, activation='relu')(hidden)])
    
    result = Dense(1, activation='sigmoid')(hidden)
    aux_result = Dense(num_aux_targets, activation='sigmoid')(hidden)
    
    model = Model(inputs=words, outputs=[result, aux_result])
    model.compile(loss='binary_crossentropy', optimizer='adam')

    return model
    
In [ ]:
train_df = pd.read_csv('./input/train.csv')
test_df = pd.read_csv('./input/test.csv')

x_train = train_df[TEXT_COLUMN].astype(str)
y_train = train_df[TARGET_COLUMN].values
y_aux_train = train_df[AUX_COLUMNS].values
x_test = test_df[TEXT_COLUMN].astype(str)

# 계산의 편의상 0.5이상은 1로, 그 이하는 0으로 통일한다.
for column in IDENTITY_COLUMNS + [TARGET_COLUMN]:
    train_df[column] = np.where(train_df[column] >= 0.5, True, False)

tokenizer = text.Tokenizer(filters=CHARS_TO_REMOVE)
tokenizer.fit_on_texts(list(x_train) + list(x_test))

x_train = tokenizer.texts_to_sequences(x_train)
x_test = tokenizer.texts_to_sequences(x_test)
x_train = sequence.pad_sequences(x_train, maxlen=MAX_LEN)
x_test = sequence.pad_sequences(x_test, maxlen=MAX_LEN)
  • 아주 간단한 로직으로, 미리미리 Sample Weight를 작성해서 빠르게 수렴하도록 한다.
  • IDENTITY컬럼에서 양성이 나왔으나, 결과적으로 해롭지 않다고 판단된 것들에 대해서는 빡세게 조정
  • 생략가능한 부분이다.
In [ ]:
sample_weights = np.ones(len(x_train), dtype=np.float32)
sample_weights += train_df[IDENTITY_COLUMNS].sum(axis=1)
sample_weights += train_df[TARGET_COLUMN] * (~train_df[IDENTITY_COLUMNS]).sum(axis=1)
sample_weights += (~train_df[TARGET_COLUMN]) * train_df[IDENTITY_COLUMNS].sum(axis=1) * 5
sample_weights /= sample_weights.mean()
  • 나온 단어들의 index를 가지고 embedding_matrix를 만든다.
In [0]:
embedding_matrix = np.concatenate(
    [build_matrix(tokenizer.word_index, f) for f in EMBEDDING_FILES], axis=-1)

checkpoint_predictions = []
weights = []
  • 최종적으로 예측을 하고 submission 파일을 만드는 부분이다.
  • 결과를 내기 위해 하나의 Model당 하나의 결과를 더해 최종적으로 앙상블 형식으로 몇개의 모델을 만든다.
In [0]:
for model_idx in range(NUM_MODELS):
    model = build_model(embedding_matrix, y_aux_train.shape[-1])
    for global_epoch in range(EPOCHS):
        model.fit(
            x_train,
            [y_train, y_aux_train],
            batch_size=BATCH_SIZE,
            epochs=1,
            verbose=2,
            sample_weight=[sample_weights.values, np.ones_like(sample_weights)],
            callbacks=[
                LearningRateScheduler(lambda _: 1e-3 * (0.55 ** global_epoch))
            ]
        )
        checkpoint_predictions.append(model.predict(x_test, batch_size=2048)[0].flatten())
        weights.append(2 ** global_epoch)

predictions = np.average(checkpoint_predictions, weights=weights, axis=0)

submission = pd.DataFrame.from_dict({
    'id': test_df.id,
    'prediction': predictions
})
submission.to_csv('submission_google.csv', index=False)
Epoch 1/1
 - 1130s - loss: 0.5261 - dense_7_loss: 0.4194 - dense_8_loss: 0.1067
Epoch 1/1
 - 1128s - loss: 0.5077 - dense_7_loss: 0.4056 - dense_8_loss: 0.1021
Epoch 1/1
 - 1133s - loss: 0.5263 - dense_11_loss: 0.4196 - dense_12_loss: 0.1067
Epoch 1/1
 - 1132s - loss: 0.5077 - dense_11_loss: 0.4055 - dense_12_loss: 0.1021

댓글 남기기