Re:ゼロから始めるML生活

どちらかといえばエミリア派です

学習済みのBERTからEmbeddingを取得する

f:id:nogawanogawa:20200818021319p:plain

最近はちょいちょいBERTとかを使って遊んでたりします。

今回は、学習済みのBERTのモデルを使って、文書Embedgingを取得してみたいと思います。

参考にさせていただいたのはこちらの記事です。

yag-ays.github.io

毎度のことながら、やることは上の記事とほとんど変わりませんが、自分の勉強のためにやってみたいと思います。

モチベーション

学習済みのBERTからEmbeddingが得られれば、それを別のモデルの特徴量として活用することができます。

この辺りの記事を見ても、文書分類に学習済みのBERTで作成したEmbeddingを特徴量として使用することで実際に効果が出るケースがあるようです。

www.m3tech.blog

リポジトリを見ると、冒頭の記事を参考にEmbeddingを使用していたので、同じようにすればいろいろ効果出そうだなーと言うのがモチベーションです。

BERTからEmbeddingを抽出する

とは言うものの、transformersのBERTを使ってみた系の記事を探してみても、なかなかEmbeddingを取り出しているケースは見当たりませんでした。 まあ、BERT単体で使用しているうちはEmbeddingは直接触る必要はないんでそうですが。

一応中間層の重みは取得できる様になっているので、やればできると思ったので、実際にやってみます。

BERTの構造とEmbeddingの取得の方針

まずはBERTのモデルの構造を示さないとなにをやっているのか分からなくなるのでそのへんから。

BERTは下記のように多くの層があり、その出力を取得することでEmbeddingとして取り扱うことができると考えられます。

f:id:nogawanogawa:20200818002253j:plain
BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding より

bert-as-serviceを参考にすれば、ここからEmbeddingを取得する際には最終層の1つ前の層を取得するようです。 (この辺は先行事例を参考にしましたが、他にもいろいろEmbeddingを取る考え方はあると思います)

試しにやってみる

試しにやってみるとこんな感じになりました。

なにも次元圧縮等をしないと768次元のベクトルになるので、それをそのまま特徴量としています。 なにはともあれ、これで学習済みのBERTからEmbeddingを取得できるようになりました。

抽出したBERTを使って含意関係認識の特徴量にしてみる

せっかく抽出できるようになったので、この前にやってたLightGBMの特徴量にBERTのEmbeddingを追加してやってみたいと思います。

BERTを用いた特徴量

こんな感じで書いてみました。

from transformers import BertJapaneseTokenizer, BertForSequenceClassification
import pandas as pd
import numpy as np
import torch

class FeaturePretrainedBert:
    def __init__(self):
        super().__init__()
        self.tokenizer = BertJapaneseTokenizer.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking')
        self.model = BertForSequenceClassification.from_pretrained(
            "cl-tohoku/bert-base-japanese-whole-word-masking", # 日本語Pre trainedモデルの指定
            num_labels = 2, # ラベル数(今回はBinayなので2、数値を増やせばマルチラベルも対応可)
            output_attentions = False, # アテンションベクトルを出力するか
            output_hidden_states = True, # 隠れ層を出力するか
        )
        self.model.eval()

    def get_embedding(self, text: str):
        tokenized_text = self.tokenizer.tokenize(text)
        indexed_tokens = self.tokenizer.convert_tokens_to_ids(tokenized_text)
        tokens_tensor = torch.tensor([indexed_tokens])

        with torch.no_grad(): # 勾配計算なし
            all_encoder_layers = self.model(tokens_tensor)

        embedding = all_encoder_layers[1][-2].numpy()[0]
        result = np.mean(embedding, axis=0)

        return result


    def get(self, primary_text: str, secondary_text: str):
        text = primary_text + "[SEP]" + secondary_text
        f8 = self.get_embedding(text)

        return f8

結果

これでやってみるとこんな感じになりました。

              precision    recall  f1-score   support

           0     0.5000    0.1798    0.2645        89
           1     0.6620    0.8994    0.7627       159

    accuracy                         0.6411       248
   macro avg     0.5810    0.5396    0.5136       248
weighted avg     0.6039    0.6411    0.5839       248

BERTを含意関係認識のために学習させたときよりは精度は落ちますが、この前のlightgbmよりは良くなってる感じですかね。

使ったコード

今回使った残骸はこちら。

github.com

参考にさせていただいた記事

今回の記事を書くにあたって、下記の記事を参考にさせていただきました。

yag-ays.github.io

www.m3tech.blog

感想

この前やっていた含意関係認識があまりにも精度が低かったので、ちょっとEmbeddingを特徴量として使ってみたくなったのでやってみた次第です。 結果、特に追加で学習しなくても多少スコアが良くなるという結果になりました。

なんとなくですが、BERTの部分だけ個別に含意関係認識用に学習させて、そのEmbeddingを取ってlightgbmに突っ込んだらスコア上がりそうだなとかは思います。

その他、参考記事によればLDA(トピックモデル)やSCDVなども特徴量として突っ込んでいるので、機会があったらそのあたりもやってみたいと思います。