メモ帳

python, juliaで機械学習をやっていく

kerasで学習済みword2vecをembedding layerに組み込む方法

kerasで学習済みword2vecをモデルに組み込む方法を紹介します。word2vecなどで学習した分散表現(token id毎のベクトル値)をkerasのembedding layerの重みに設定し、新たに学習させないように指定するという流れです。こうすることで、word2vecによる特徴量抽出を行うモデルがkerasで構成できます。自然言語処理では頻出の処理だと思います。前処理の段階で分散表現を獲得する(layerに組み込まない)手もあるかとは思いますが、prediction用に別途前処理用のコードを書く必要がでてくるなどの手間が発生するので、できるだけ今回紹介するようにモデルに加えるほうが無難だと思います。

使用するフレームワーク

  • 分散表現獲得: gensim v3.8.0
  • NN構築: keras(tensorflow backend) v2.2.5

データセット

IMDB Movie reviews sentiment classification datasetを使用します。映画のレビューコメントの感情分析(positive or negative)のアノテーションデータです。keras.datasetsから簡単に取得できます. Datasets - Keras Documentation (今回はモデルには学習させないのでデータセットは何でもいいです)

取得するデータは前処理済みのtoken id(int)のリストになっています。gensimの仕様に合わせるためにここでtoken idを文字列に変換しておきます。

from keras.datasets import imdb

num_words = 1000
(x_train, y_train), (x_test, y_test) = imdb.load_data(path="imdb.npz",
                                                      num_words=num_words)
x_train = [[str(f) for f in x] for x in x_train] # ID文字列のリストに変換

word2vecでの分散表現の学習

gensimを使ってword2vecの学習を行います。wikipediaデータなどの大規模コーパスを使用するのが一般的だと思いますが、ここでは簡単のためにtrain dataを使います。

from gensim.models import Word2Vec

 # 分散表現の次元
w2v_size = 100
 # 学習開始
w2v = Word2Vec(x_train, size=w2v_size, min_count=3, window=3)

gensimのword2vecからembedding layer用の重み行列を作る

先程学習したモデルはデータセットの全てのtoken idに対する分散表現(ベクトル値)をもっています。これをembedding layerの重みにします。すると、モデルにword2vecを組み込んだ形になります。ただし、keras.layers.Embeddingで指定するために行列の形式に変換する必要があります。

def get_embedding_matrix(model, word_index):
    """
    keras.layers.Embeddingのweights引数で指定するための重み行列作成
    model: gensim model
    num_word: modelのvocabularyに登録されている単語数
    emb_dim: 分散表現の次元
    word_index: gensim modelのvocabularyに登録されている単語名をkeyとし、token idをvalueとする辞書 ex) {'word A in gensim model vocab': integer token id} 
    """
    # gensim modelの分散表現を格納するための変数を宣言
    embedding_matrix = np.zeros((max(list(word_index.values())) + 1, model.vector_size), dtype="float32")
   
    # 分散表現を順に行列に格納する
    for word, label in word_index.items():
        try:
            # gensimのvocabularyに登録している文字列をembedding layerに入力するone-hot vectorのインデックスに変換して、該当する重み行列の要素に分散表現を代入
            embedding_matrix[label] = model.wv[word]
        except KeyError:
            pass
    return embedding_matrix

実行例

上記関数は以下のように呼び出します。

# word_indexの作成
word_index = {key: int(key) for key in w2v.wv.vocab}

# ゼロ埋め用(入力の時系列方向の長さを揃えるためにtoken id=0でpaddingする想定)にindexを追加
word_index['0'] = 0

# embedding_matrixを作成
emb_mat = get_embedding_matrix(w2v, word_index)

emb_mat.shape
# >>> (1000, 100)

keras.layers.Embeddingの宣言法

先程作成したembedding_matrixをweighs引数に代入します。また、embedding layerが追加で学習しないようにtrainable=Falseとします。

from keras.layers import Embedding

# 単語数、embeddingの次元
num_words, w2v_size = embedding_matrix.shape

# embedding
Embedding(num_words, w2v_size,
                        weights=[embedding_matrix], 
                        input_length=max_len, # 時系列方向の大きさ(最大入力単語数)
                       trainable=False)

modelの例

LSTMを使ったシンプルなモデル例を挙げておきます。

# 最大入力単語数
max_len = pd.DataFrame(x_train)[0].apply(lambda x: len(x)).max()
# >> 2494

# 単語数、embeddingの次元
num_words, w2v_size = embedding_matrix.shape

# モデルの構築
model = Sequential()
model.add(Embedding(num_words, w2v_size,
                    weights=[embedding_matrix], 
                    input_length=max_len,
                   trainable=False))
model.add(LSTM(50, batch_input_shape=(None, max_len, w2v_size), return_sequences=False))
model.add(Dense(25))
model.add(Dense(2))
model.add(Activation("sigmoid"))

model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding_2 (Embedding)      (None, 2494, 100)         100000    
_________________________________________________________________
lstm_1 (LSTM)                (None, 50)                30200     
_________________________________________________________________
dense_1 (Dense)              (None, 25)                1275      
_________________________________________________________________
dense_2 (Dense)              (None, 2)                 52        
_________________________________________________________________
activation_1 (Activation)    (None, 2)                 0         
=================================================================
Total params: 131,527
Trainable params: 31,527
Non-trainable params: 100,000
_________________________________________________________________

サマリーを見るとembeddingのParam #=100000であり、Non-trainable params: 100000なのでembedding layerのフリーズ(重みを学習しない)は成功していそうです。gensimを使った分散表現を用いると、大幅にNeural Networkの学習を行う重みの数を削減できることが確認できました。 BERT以前はword2vecで学習済みのembedding layerを導入することで大幅に精度が向上したそうです。既に一般的な手法であり、ベースラインとして使ったり、リソースをあまり割きたくない時に便利な手法ですので、ぜひ興味があればお試し下さい!