BERT Base line−コメント付き (LB 0.7387 ) by Oregin

BERT Base line−コメント付き (LB 0.7387 ) by Oregin

研究論文の国際学会採択予測で、件名と概要を利用した簡単なBERTのサンプルコードです。まだ、BERTについて勉強し始めたばかりなので、精度や見栄えはイマイチですが、なんとか動くものを作れました。
ご参考までご活用ください。

※Google Colab(GPU利用)で実行可能です。

LB= 0.7387 でした。

ディレクトリ構成

  • base_path : このファイルを入れておくディレクトリ(各種パスの設定にて、保存した絶対パスを指定してください)
  • base_path/data : test_data.csv,train_data.csv,submission.csvを入れておくディレクトリ
  • base_path/model : 学習済みのモデルを保存するディレクトリ
  • base_path/output : 提出用ファイルを保存するディレクトリ

ライブラリのインポート

!pip install transformers
# 各種ライブラリのインポート
import os
import pandas as pd
import itertools
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
from torch.nn.utils.rnn import pad_sequence
from transformers import BertModel, BertTokenizer
from sklearn.model_selection import train_test_split
from tqdm import tqdm
import warnings
warnings.filterwarnings('ignore')

各種パスの設定

# 各種パスの設定
######################################################################
base_path = 'XXXXXXXXXX' # ベースとなるパスを指定してください。#######
######################################################################
os.makedirs(os.path.join(base_path,'model'), exist_ok=True)  # 学習済みモデルの保存するディレクトリを作成
os.makedirs(os.path.join(base_path,'output'), exist_ok=True)  # 提出用ファイルを出力するディレクトリを作成
train_data_path = os.path.join(base_path,'data/train_data.csv') # 訓練データのパスを指定
test_data_path = os.path.join(base_path,'data/test_data.csv') # テストデータのパスを指定
submit_data_path = os.path.join(base_path,'data/submission.csv') # 提出用サンプルfileのパスを指定
model_file_path = os.path.join(base_path,'model/best_model.pt') # 学習済みモデルのパスを指定
output_file_path = os.path.join(base_path,'output/001_submission.csv') # 提出用ファイルのパスを指定

各種データの読み込み

# 各種データの読込
df = pd.read_csv(train_data_path) # 訓練データの読込
test_df = pd.read_csv(test_data_path) # テストデータの読込
test_df['y'] = 0 # テストデータのYの値を初期化
submit_df = pd.read_csv(submit_data_path) # 提出用散布リファイルの読込

関数の定義

# BERTを使用してトークナイズするためのクラスを定義します。
class BERTTokenize(nn.Module):
    def __init__(self):
        super(BERTTokenize, self).__init__()
        self.tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')

    def forward(self, text):
        return self.tokenizer.encode_plus(
            text,
            max_length=512,
            add_special_tokens=True,
            return_token_type_ids=False,
            padding='max_length',
            return_attention_mask=True,
            return_tensors='pt',
            truncation=True
        )
# データセットを作成するクラスを定義します。
class PaperDataset(Dataset):
    def __init__(self, dataframe, tokenizer):
        self.data = dataframe
        self.tokenizer = tokenizer

    def __len__(self):
        return len(self.data)

    def __getitem__(self, index):
        title = self.data.iloc[index]['title']
        abstract = self.data.iloc[index]['abstract']
        text = title + ' ' + abstract
        label = self.data.iloc[index]['y']
        inputs = self.tokenizer(text)
        return {
            'input_ids': inputs['input_ids'].flatten(),
            'attention_mask': inputs['attention_mask'].flatten(),
            'targets': torch.tensor(label, dtype=torch.float)
        }
# datasetで定義されたバッチ形式の__getitem__を処理して、オリジナルのtorch.Tensorにする関数
def collate_fn(batch):
    input_ids = pad_sequence([torch.tensor(item["input_ids"]) for item in batch], batch_first=True)
    attention_mask = pad_sequence([torch.tensor(item["attention_mask"]) for item in batch], batch_first=True)
    labels = torch.tensor([item['targets'] for item in batch])
    return {"input_ids": input_ids, "attention_mask": attention_mask, 'targets': labels}
# BERTのモデルのクラスを定義します。
class BERTModel(nn.Module):
    def __init__(self):
        super(BERTModel, self).__init__()
        self.bert = BertModel.from_pretrained('bert-base-uncased')
        self.dropout = nn.Dropout(0.3)
        self.fc = nn.Linear(768, 1)
        self.sig = nn.Sigmoid()

    def forward(self, input_ids, attention_mask):
        outputs = self.bert(input_ids, attention_mask=attention_mask,token_type_ids=None)
        pooled_output = outputs[1]
        dropout_output = self.dropout(pooled_output)
        output = self.fc(dropout_output)
        return self.sig(output)

データローダの作成

#トレーニングとバリデーションのためのデータローダーを作成します。
train_df, val_df = train_test_split(df, test_size=0.2, random_state=42)

tokenizer = BERTTokenize()

train_dataset = PaperDataset(train_df, tokenizer)
val_dataset = PaperDataset(val_df, tokenizer)

train_loader = DataLoader(train_dataset, batch_size=8, shuffle=True, collate_fn=collate_fn)
val_loader = DataLoader(val_dataset, batch_size=8, collate_fn=collate_fn)
Downloading (…)solve/main/vocab.txt:   0%|          | 0.00/232k [00:00<?, ?B/s]
Downloading (…)okenizer_config.json:   0%|          | 0.00/28.0 [00:00<?, ?B/s]
Downloading (…)lve/main/config.json:   0%|          | 0.00/570 [00:00<?, ?B/s]

モデルの作成

# 損失関数とオプティマイザーを定義して、モデルを作成します。
model = BERTModel()
criterion = nn.BCELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-5)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)

学習の実行

# 学習の関数の定義
def train(model, dataloader, optimizer, criterion):
    model.train()
    train_loss = 0
    for batch in dataloader:
        input_ids = batch['input_ids'].to(device)
        attention_mask = batch['attention_mask'].to(device)
        targets = batch['targets'].view(-1, 1).to(device)

        optimizer.zero_grad()
        outputs = model(input_ids, attention_mask)
        loss = criterion(outputs, targets)
        loss.backward()
        optimizer.step()

        train_loss += loss.item()

    return train_loss / len(dataloader)
# 検証の関数の定義
def validate(model, dataloader, criterion):
    model.eval()
    val_loss = 0
    with torch.no_grad():
        for batch in dataloader:
            input_ids = batch['input_ids'].to(device)
            attention_mask = batch['attention_mask'].to(device)
            targets = batch['targets'].view(-1, 1).to(device)

            outputs = model(input_ids, attention_mask)
            loss = criterion(outputs, targets)

            val_loss += loss.item()

    return val_loss / len(dataloader)
# 学習・検証の実行

best_val_loss = float('inf')

for epoch in tqdm(range(10)):
    train_loss = train(model, train_loader, optimizer, criterion)
    val_loss = validate(model, val_loader, criterion)

    if val_loss < best_val_loss:
        best_val_loss = val_loss
        torch.save(model.state_dict(), model_file_path) # ベストなモデルを保存する

    print(f'Epoch {epoch+1} - train loss: {train_loss:.3f}, val loss: {val_loss:.3f}')

テストデータで予測する

# モデルの読み込み
model.load_state_dict(torch.load(model_file_path))
# テストデータのデータローダ―の作成
tokenizer = BERTTokenize()

test_dataset = PaperDataset(test_df, tokenizer)
test_loader = DataLoader(test_dataset, batch_size=8, shuffle=False,collate_fn=collate_fn)
# 予測する関数の定義
def predict(model, dataloader):
  outputs = []
  with torch.no_grad():
    for batch in dataloader:
      input_ids = batch['input_ids'].to(device)
      attention_mask = batch['attention_mask'].to(device)
      # モデルによる予測
      output = model(input_ids, attention_mask=attention_mask)
      outputs.extend(list(itertools.chain.from_iterable(output.tolist())))
            
  return outputs

#予測の実行
outputs = predict(model, test_loader)
#予測結果を提出形式に合わせて出力
submit_df['y'] = pd.Series(outputs)
submit_df['y'].mask(submit_df['y'] > 0.5,1,inplace=True)
submit_df['y'].mask(submit_df['y'] < 1,0,inplace=True)
submit_df['y'] = submit_df['y'].astype('int')
submit_df.to_csv(output_file_path,index = False)

添付データ

  • BERT_Base_line_by_Oregin.ipynb?X-Amz-Expires=10800&X-Amz-Date=20240420T061515Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIP7GCBGMWPMZ42PQ
  • Favicon
    new user
    コメントするには 新規登録 もしくは ログイン が必要です。