SciBERTを用いたtextデータの特徴量抽出

はじめに

今回のデータではtitleやabstractといったtextデータから特徴量を抽出することが鍵となりそうです。このディスカッションではBERTといわれる自然言語処理モデルの中で、SciBERTという科学論文誌で事前学習したモデルで特徴量抽出する手法を簡単に紹介します。

実装紹介

まずSciBERTのpre-training modelを取得しましょう。 https://github.com/allenai/scibert このreadmeのPyTorch HunggingFace Modelsからダウンロードします。 この際tar拡張子でダウンロードするため解凍する必要があります。

![831dd928-d837-4749-9694-cb383244c17d.png](https://probspace-stg.s3-ap-northeast-1.amazonaws.com/uploads/user/3829af4975d54ab6a4fb9e6732d93d8d/images/UserImage_153/831dd928-d837-4749-9694-cb383244c17d.png =300x)

次に実装です。実装はatmaCup#10のディスカッションにあがっているkaeruruさんの「[オランダ語対応] 事前学習済み BERT モデルを使ったテキスト特徴抽出について」を参考にしています。

GPUを用いなければかなり時間がかかるため、GPU環境がない方はGoogle Colaboratoryを用いることをお勧めします。

import pandas as pd
import numpy as np
import torch
import transformers

from pathlib import Path
from transformers import BertTokenizer
from tqdm import tqdm
tqdm.pandas()

class BertSequenceVectorizer:
    def __init__(self):
        self.device = 'cuda' if torch.cuda.is_available() else 'cpu'
        self.model_name = 'models/scibert_scivocab_uncased' # modelを保存しているpathを指定
        self.tokenizer = BertTokenizer.from_pretrained(self.model_name)
        self.bert_model = transformers.BertModel.from_pretrained(self.model_name)
        self.bert_model = self.bert_model.to(self.device)
        self.max_len = 128
        print(self.device)


    def vectorize(self, sentence : str) -> np.array:
        inp = self.tokenizer.encode(sentence)
        len_inp = len(inp)

        if len_inp >= self.max_len:
            inputs = inp[:self.max_len]
            masks = [1] * self.max_len
        else:
            inputs = inp + [0] * (self.max_len - len_inp)
            masks = [1] * len_inp + [0] * (self.max_len - len_inp)

        inputs_tensor = torch.tensor([inputs], dtype=torch.long).to(self.device)
        masks_tensor = torch.tensor([masks], dtype=torch.long).to(self.device)

        bert_out = self.bert_model(inputs_tensor, masks_tensor)
        seq_out, pooled_out = bert_out['last_hidden_state'], bert_out['pooler_output']

        if torch.cuda.is_available():    
            return seq_out[0][0].cpu().detach().numpy()
        else:
            return seq_out[0][0].detach().numpy()

DATA_DIR = Path('data') # dataディレクトリを指定
FEATURES_DIR = Path('features') # 特徴量を保存するディレクトリを指定

# 自分は読み込み速度をあげるためpickleファイルに書き直しています
train = pd.read_pickle(DATA_DIR / 'train_data_doi.pickle')
test = pd.read_pickle(DATA_DIR / 'test_data.pickle')

BSV = BertSequenceVectorizer()
for col in ['title', 'abstract']: 
    train_bert = train[col].fillna('nan').progress_apply(lambda x: BSV.vectorize(x))
    test_bert = test[col].fillna('nan').progress_apply(lambda x: BSV.vectorize(x))
    pd.DataFrame(train_bert.tolist(),columns=[f'{col}_scibert_{i}' for i in range(768)]).to_pickle(FEATURES_DIR / f'scibert_train_{col}.pickle')
    pd.DataFrame(test_bert.tolist(),columns=[f'{col}_scibert_{i}' for i in range(768)]).to_pickle(FEATURES_DIR / f'scibert_test_{col}.pickle')

精度感について

自分は特徴量抽出後PCAで10次元に圧縮しました。 入れる前のCVは0.4934、LB0.4932、入れた後はCV:0.4927、LB:0.4921と0.01ほどスコアに貢献しました。

おわりに

@0.49切られている方へ つよいとくちょうりょうをおしえてください!

Aws4 request&x amz signedheaders=host&x amz signature=5a7af5260489359110985823accc8d5f19eaf943bc86ead395bb3a9afbbb81cd
katwooo414

SciBERTのご紹介ありがとうございます。 w2vやtfidfからこちらのSciBERTに変更したところCVが改善しました。(LBについてはまだ確認できていません。)
また、[CLS]のベクトルだけを利用するよりも[CLS]と[SEP]を取り除いた要素のベクトルの平均値を利用したほうがCVの改善具合が大きかったです。
・[CLS]だけ:CVが0.01改善
・[CLS]と[SEP]以外の平均:CVが0.03改善

(まだ自分がBERTの使い方について理解しきれていないため、意図していることがちゃんとコードに落とし込めているかは正直微妙ですが。)

Favicon
new user
コメントするには 新規登録 もしくは ログイン が必要です。