最近はちょいちょいBERTとかを使って遊んでたりします。
今回は、学習済みのBERTのモデルを使って、文書Embedgingを取得してみたいと思います。
参考にさせていただいたのはこちらの記事です。
毎度のことながら、やることは上の記事とほとんど変わりませんが、自分の勉強のためにやってみたいと思います。
モチベーション
学習済みのBERTからEmbeddingが得られれば、それを別のモデルの特徴量として活用することができます。
この辺りの記事を見ても、文書分類に学習済みのBERTで作成したEmbeddingを特徴量として使用することで実際に効果が出るケースがあるようです。
リポジトリを見ると、冒頭の記事を参考にEmbeddingを使用していたので、同じようにすればいろいろ効果出そうだなーと言うのがモチベーションです。
BERTからEmbeddingを抽出する
とは言うものの、transformersのBERTを使ってみた系の記事を探してみても、なかなかEmbeddingを取り出しているケースは見当たりませんでした。 まあ、BERT単体で使用しているうちはEmbeddingは直接触る必要はないんでそうですが。
一応中間層の重みは取得できる様になっているので、やればできると思ったので、実際にやってみます。
BERTの構造とEmbeddingの取得の方針
まずはBERTのモデルの構造を示さないとなにをやっているのか分からなくなるのでそのへんから。
BERTは下記のように多くの層があり、その出力を取得することでEmbeddingとして取り扱うことができると考えられます。
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よりは良くなってる感じですかね。
使ったコード
今回使った残骸はこちら。
参考にさせていただいた記事
今回の記事を書くにあたって、下記の記事を参考にさせていただきました。
感想
この前やっていた含意関係認識があまりにも精度が低かったので、ちょっとEmbeddingを特徴量として使ってみたくなったのでやってみた次第です。 結果、特に追加で学習しなくても多少スコアが良くなるという結果になりました。
なんとなくですが、BERTの部分だけ個別に含意関係認識用に学習させて、そのEmbeddingを取ってlightgbmに突っ込んだらスコア上がりそうだなとかは思います。
その他、参考記事によればLDA(トピックモデル)やSCDVなども特徴量として突っ込んでいるので、機会があったらそのあたりもやってみたいと思います。