チュートリアルセンチメント分析のためのBERTの微調整

チュートリアルセンチメント分析のためのBERTの微調整

    Originally published by Skim AI's Machine Learning Researcher, Chris Tran.



BERT_for_Sentiment_Analysis





A - はじめに

近年、NLPコミュニティは自然言語処理、特に転移学習への移行において多くのブレークスルーを見てきた。ELMo、fast.aiのULMFiT、Transformer、OpenAIのGPTのようなモデルは、研究者が複数のベンチマークで最先端の結果を達成することを可能にし、高性能で大規模な事前学習済みモデルをコミュニティに提供した。NLPにおけるこのシフトは、NLPのImageNetの瞬間と見なされている。数年前のコンピュータビジョンにおけるシフトでは、特定のタスクで訓練された数百万のパラメーターを持つディープラーニングネットワークの低レイヤーを、ゼロから新しいネットワークを訓練するのではなく、他のタスクのために再利用し、微調整することができた。

最近のNLPの進化における最も大きなマイルストーンの一つは、GoogleのBERTのリリースであり、これはNLPの新しい時代の始まりと言われている。このノートブックでは、HuggingFaceの 変圧器 ライブラリを使用して、分類タスク用に事前訓練された BERT モデルを微調整する。次に、BERT の性能を、TF-IDF ベクタライザとナイーブ・ベイズ分類器を使用したベースライン・モデルと比較する。このモデルには、TF-IDF ベクタライザと Naive Bayes 分類器を使用する。 変圧器 ライブラリは、最先端の BERT モデルを迅速かつ効率的に微調整し、精度を高めるのに役立ちます。 10% ベースラインモデルよりも高い。

参考:

理解するために 変圧器 (BERT が構築されているアーキテクチャ)および BERT の実装方法を学ぶには、以下のソースを読むことを強く推奨します:

B - セットアップ

1.必須ライブラリのロード

0]である:

import os
import re
from tqdm import tqdm
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

2.データセット

2.1.データセットダウンロード

0]である:

# Download data
import requests
request = requests.get("https://drive.google.com/uc?export=download&id=1wHt8PsMLsfX5yNSqrt2fSTcb8LEiclcf")
with open("data.zip", "wb") as file:
    file.write(request.content)
# Unzip data
import zipfile
with zipfile.ZipFile('data.zip') as zip:
    zip.extractall('data')

2.2.ロードトレインデータ

列車データには2つのファイルがあり、それぞれに1700件の苦情/非苦情ツイートが含まれている。データ内のすべてのツイートには、少なくとも航空会社のハッシュタグが含まれている。

訓練データをロードし、ラベルを付ける。分類に使うのはテキスト・データだけなので、重要でない列は削除して アイドル, ツイート そして ラベル コラム
0]である:

 # Load data and set labels
data_complaint = pd.read_csv('data/complaint1700.csv')
data_complaint['label'] = 0
data_non_complaint = pd.read_csv('data/noncomplaint1700.csv')
data_non_complaint['label'] = 1
# Concatenate complaining and non-complaining data
data = pd.concat([data_complaint, data_non_complaint], axis=0).reset_index(drop=True)
# Drop 'airline' column
data.drop(['airline'], inplace=True, axis=1)
# Display 5 random samples
data.sample(5)

アウト[0]:

アイドル ツイート ラベル
1988 24991 なんて素晴らしいおかえりなさい。笑える。デプレーニン... 1
1294 72380 今夜の@JetBlueにはとてもがっかりした。フライト... 0
1090 127893 ユナイテッドの友人たちは大変な思いをしている...。 0
553 58278 ユナイテッド...僕がXmasに欲しいのは、ロストバッグなんだ... 0
2075 30695 うん、正直に言うと......試してみたいんだ......。 1

訓練データ全体をランダムに2つの集合に分割する:90%のデータを含む訓練集合と10%のデータを含む検証集合である。訓練セットでクロスバリデーションを用いてハイパーパラメータのチューニングを行い、検証セットでモデルの比較を行う。
0]である:

from sklearn.model_selection import train_test_split
X = data.tweet.values
y = data.label.values
X_train, X_val, y_train, y_val =
    train_test_split(X, y, test_size=0.1, random_state=2020)

2.3.負荷試験データ

テストデータにはラベルのない4555例が含まれる。約300例は文句のないツイートである。私たちのタスクは アイドル そして、我々の結果が正しいかどうかを手作業で検証する。
0]である:

# Load test data
test_data = pd.read_csv('data/test_data.csv')
# Keep important columns
test_data = test_data[['id', 'tweet']]
# Display 5 samples from the test data
test_data.sample(5)

アウト[0]:

アイドル ツイート
1539 59336 アメリカン航空のフライトが2時間以上遅れた。
607 24101 まだこのエラーメッセージが表示されます。
333 13179 ジェットブルーの飛行機に乗るために#SeaTacで待っている...
2696 102948 事前の座席指定プロを通すのが嫌なんだ...。
3585 135638 恥を知れ

3.トレーニング用GPUのセットアップ

Google ColabはGPUとTPUを無料で提供している。大規模なニューラルネットワークをトレーニングするので、これらの機能を利用するのがベストだ。

GPUを追加するには、メニューから「GPU」を選択する:

ランタイム -> ランタイムタイプの変更 -> ハードウェアアクセラレータ:GPU

次に、以下のセルを実行して、デバイスとしてGPUを指定する必要があります。
0]である:

import torch
if torch.cuda.is_available():       
    device = torch.device("cuda")
    print(f'There are {torch.cuda.device_count()} GPU(s) available.')
    print('Device name:', torch.cuda.get_device_name(0))
else:
    print('No GPU available, using the CPU instead.')
    device = torch.device("cpu")
利用可能なGPUは1つです。
デバイス名: Tesla T4

C - ベースライン:TF-IDF + Naive Bayes分類器

このベースラインアプローチでは、まずTF-IDFを使ってテキストデータをベクトル化する。次に、ナイーブベイズモデルを分類器として使用する。

なぜナイーブ・ベイズなのか?Random Forest、Support Vectors Machine、XGBoostなど、さまざまな機械学習アルゴリズムを試したが、Naive Bayesが最も優れた性能を発揮することがわかった。それは Scikit-learnのガイド また、適切な推定量を選択するために、テキストデータにはNaive Bayesを使用することが推奨されている。また、SVDを使って次元を減らしてみたが、それ以上の性能は得られなかった。

1.データの準備

1.1.前処理

Bag-of-Words モデルでは、テキストは文法や語順を無視して、単語の袋として表現される。したがって、ストップワード、句読点、文の意味にあまり寄与しない文字を削除したい。
0]である:

import nltk
# Uncomment to download "stopwords"
nltk.download("stopwords")
from nltk.corpus import stopwords
def text_preprocessing(s):
    """
    - Lowercase the sentence
    - Change "'t" to "not"
    - Remove "@name"
    - Isolate and remove punctuations except "?"
    - Remove other special characters
    - Remove stop words except "not" and "can"
    - Remove trailing whitespace
    """
    s = s.lower()
    # Change 't to 'not'
    s = re.sub(r"'t", " not", s)
    # Remove @name
    s = re.sub(r'(@.*?)[s]', ' ', s)
    # Isolate and remove punctuations except '?'
    s = re.sub(r'(['".()!?\/,])', r' 1 ', s)
    s = re.sub(r'[^ws?]', ' ', s)
    # Remove some special characters
    s = re.sub(r'([;:|•«n])', ' ', s)
    # Remove stopwords except 'not' and 'can'
    s = " ".join([word for word in s.split()
                  if word not in stopwords.words('english')
                  or word in ['not', 'can']])
    # Remove trailing whitespace
    s = re.sub(r's+', ' ', s).strip()
    return s
[nltk_data] パッケージ stopwords を /root/nltk_data にダウンロードしています...
[nltk_data] stopwords パッケージは既に最新です!

1.2.TF-IDFベクトライザ

情報検索において、 TF-IDFの略である。 用語頻度-逆文書頻度TF-IDFは、ある単語がコレクションやコーパスの文書にとってどれだけ重要であるかを反映することを目的とした数値統計である。TF-IDFを使ってテキストデータをベクトル化し、機械学習アルゴリズムに渡す。
0]である:

%%time
from sklearn.feature_extraction.text import TfidfVectorizer
# Preprocess text
X_train_preprocessed = np.array([text_preprocessing(text) for text in X_train])
X_val_preprocessed = np.array([text_preprocessing(text) for text in X_val])
# Calculate TF-IDF
tf_idf = TfidfVectorizer(ngram_range=(1, 3),
                         binary=True,
                         smooth_idf=False)
X_train_tfidf = tf_idf.fit_transform(X_train_preprocessed)
X_val_tfidf = tf_idf.transform(X_val_preprocessed)
CPU時間:ユーザー5.47秒、システム519ミリ秒、合計5.99秒519ミリ秒、合計:5.99秒
ウォール時間:6秒

2.ナイーブ・ベイズ分類器を訓練する

2.1.ハイパーパラメータの調整

モデルのハイパーパラメータを調整するために、クロスバリデーションとAUCスコアを使用する。関数 GET_AUC_CV はクロスバリデーションからの平均AUCスコアを返す。
0]である:

from sklearn.model_selection import StratifiedKFold, cross_val_score
def get_auc_CV(model):
    """
    Return the average AUC score from cross-validation.
    """
    # Set KFold to shuffle data before the split
    kf = StratifiedKFold(5, shuffle=True, random_state=1)
    # Get AUC scores
    auc = cross_val_score(
        model, X_train_tfidf, y_train, scoring="roc_auc", cv=kf)
    return auc.mean()

について 多項式NB クラスが持つハイパーパラメータは1つだけです。 アルファ.以下のコードは、CV AUCスコアが最も高くなるアルファ値を見つけるのに役立つ。
0]である:

from sklearn.naive_bayes import MultinomialNB
res = pd.Series([get_auc_CV(MultinomialNB(i))
                 for i in np.arange(1, 10, 0.1)],
                index=np.arange(1, 10, 0.1))
best_alpha = np.round(res.idxmax(), 2)
print('Best alpha: ', best_alpha)
plt.plot(res)
plt.title('AUC vs. Alpha')
plt.xlabel('Alpha')
plt.ylabel('AUC')
plt.show()
ベスト・アルファ  1.3

2.2.検証セットでの評価

モデルの性能を評価するために、検証セットにおけるモデルの精度とAUCスコアを計算する。
0]である:

from sklearn.metrics import accuracy_score, roc_curve, auc
def evaluate_roc(probs, y_true):
    """
    - Print AUC and accuracy on the test set
    - Plot ROC
    @params    probs (np.array): an array of predicted probabilities with shape (len(y_true), 2)
    @params    y_true (np.array): an array of the true values with shape (len(y_true),)
    """
    preds = probs[:, 1]
    fpr, tpr, threshold = roc_curve(y_true, preds)
    roc_auc = auc(fpr, tpr)
    print(f'AUC: {roc_auc:.4f}')
    # Get accuracy over the test set
    y_pred = np.where(preds >= 0.5, 1, 0)
    accuracy = accuracy_score(y_true, y_pred)
    print(f'Accuracy: {accuracy*100:.2f}%')
    # Plot ROC AUC
    plt.title('Receiver Operating Characteristic')
    plt.plot(fpr, tpr, 'b', label = 'AUC = %0.2f' % roc_auc)
    plt.legend(loc = 'lower right')
    plt.plot([0, 1], [0, 1],'r--')
    plt.xlim([0, 1])
    plt.ylim([0, 1])
    plt.ylabel('True Positive Rate')
    plt.xlabel('False Positive Rate')
    plt.show()

TF-IDFとナイーブベイズ・アルゴリズムを組み合わせることで、以下の精度を達成した。 72.65% を検証セットに適用した。この値はベースライン性能であり、微調整 BERT モデルの性能評価に使用される。
0]である:

# Compute predicted probabilities
nb_model = MultinomialNB(alpha=1.8)
nb_model.fit(X_train_tfidf, y_train)
probs = nb_model.predict_proba(X_val_tfidf)
# Evaluate the classifier
evaluate_roc(probs, y_val)
AUC0.8451
精度:75.59%

D - BERTの微調整

1.ハギング・フェイス・ライブラリーをインストールする

Hugging Faceのトランスフォーマー・ライブラリーには、BERT(Googleのもの)、GPT(OpenAIのもの)...などの最先端のNLPモデルのPyTorch実装と、事前に訓練されたモデルの重みが含まれています。
1]にある:

#!ピップがトランスを設置

2.トークン化と入力フォーマット

テキストをトークン化する前に、エンティティの言及(例:@united)や特殊文字の除去など、テキストに若干の処理を行う。BERT は文全体を使って訓練されているため、ここでの処理レベルは以前のアプローチよりもはるかに低い。
0]である:

def text_preprocessing(text):
    """
    - Remove entity mentions (eg. '@united')
    - Correct errors (eg. '&' to '&')
    @param    text (str): a string to be processed.
    @return   text (Str): the processed string.
    """
    # Remove '@name'
    text = re.sub(r'(@.*?)[s]', ' ', text)
    # Replace '&' with '&'
    text = re.sub(r'&', '&', text)
    # Remove trailing whitespace
    text = re.sub(r's+', ' ', text).strip()
    return text

0]である:

# Print sentence 0
print('Original: ', X[0])
print('Processed: ', text_preprocessing(X[0]))
オリジナル  ユナイテッド航空に問題が発生しました。昨日、搭乗予定時刻の24時間後に予約し直したのですが、今ログオン&チェックインができません。助けてもらえますか?
処理しました:  問題があります。昨日、搭乗予定時刻の24時間後に予約し直したのですが、今ログオン&チェックインができません。助けてもらえますか?

2.1.BERT トーケナイザ

事前訓練された BERT を適用するには、ライブラリによって提供されるトークナイザを使用する必要があります。これは、(1) モデルには特定の固定語彙があり、(2) BERT トークナイザには語彙外の単語を処理する特定の方法があるためです。

さらに、各文の最初と最後に特別なトークンを追加し、すべての文を一定の長さにパッド&トランケートし、「アテンション・マスク」で何がパディングトークンであるかを明示的に指定することが求められる。

について エンコード・プラス メソッドを使用します:

(1) テキストをトークンに分割する、

(2) スペシャルを追加する [CLS] そして [SEP】。] トークン、そして

(3) これらのトークンをトークナイザ語彙のインデックスに変換する、

(4) 文章の長さを最大にするために、パディングまたはトランケートする。

(5) アテンションマスクを作る。
0]である:

from transformers import BertTokenizer
# Load the BERT tokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased', do_lower_case=True)
# Create a function to tokenize a set of texts
def preprocessing_for_bert(data):
    """Perform required preprocessing steps for pretrained BERT.
    @param    data (np.array): Array of texts to be processed.
    @return   input_ids (torch.Tensor): Tensor of token ids to be fed to a model.
    @return   attention_masks (torch.Tensor): Tensor of indices specifying which
                  tokens should be attended to by the model.
    """
    # Create empty lists to store outputs
    input_ids = []
    attention_masks = []
    # For every sentence...
    for sent in data:
        # エンコード・プラス will:
        #    (1) Tokenize the sentence
        #    (2) Add the [CLS] そして [SEP】。] token to the start and end
        #    (3) Truncate/Pad sentence to max length
        #    (4) Map tokens to their IDs
        #    (5) Create attention mask
        #    (6) Return a dictionary of outputs
        encoded_sent = tokenizer.encode_plus(
            text=text_preprocessing(sent),  # Preprocess sentence
            add_special_tokens=True,        # Add [CLS] そして [SEP】。]
            max_length=MAX_LEN,                  # Max length to truncate/pad
            pad_to_max_length=True,         # Pad sentence to max length
            #return_tensors='pt',           # Return PyTorch tensor
            return_attention_mask=True      # Return attention mask
            )
        # Add the outputs to the lists
        input_ids.append(encoded_sent.get('input_ids'))
        attention_masks.append(encoded_sent.get('attention_mask'))
    # Convert lists to tensors
    input_ids = torch.tensor(input_ids)
    attention_masks = torch.tensor(attention_masks)
    return input_ids, attention_masks

トークン化の前に、文の最大長を指定する必要がある。
0]である:

# Concatenate train data and test data
all_tweets = np.concatenate([data.tweet.values, test_data.tweet.values])
# Encode our concatenated data
encoded_tweets = [tokenizer.encode(sent, add_special_tokens=True) for sent in all_tweets]
# Find the maximum length
max_len = max([len(sent) for sent in encoded_tweets])
print('Max length: ', max_len)
最大長:68

では、データをトークン化しよう。
0]である:

# Specify MAX_LEN
MAX_LEN = 64
# Print sentence 0 and its encoded token ids
token_ids = list(preprocessing_for_bert([X[0]])[0].squeeze().numpy())
print('Original: ', X[0])
print('Token IDs: ', token_ids)
# Run function ベルトの前処理 on the train set and the validation set
print('Tokenizing data...')
train_inputs, train_masks = preprocessing_for_bert(X_train)
val_inputs, val_masks = preprocessing_for_bert(X_val)
オリジナル  ユナイテッド航空で問題が発生しました。昨日、搭乗予定時刻の24時間後に予約し直したのですが、今ログオン&チェックインができません。助けてもらえますか?
トークンID  [101, 1045, 1005, 1049, 2383, 3314, 1012, 7483, 1045, 2128, 8654, 2098, 2005, 2484, 2847, 2044, 1045, 2001, 4011, 2000, 4875, 1010, 2085, 1045, 2064,1005, 1056, 8833, 2006, 1004, 4638, 1999, 1012, 2064, 2017, 2393, 1029, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
データをトークン化...

2.2.PyTorch DataLoaderの作成

torch DataLoaderクラスを使ってデータセットのイテレータを作成します。これにより、学習時のメモリを節約し、学習速度を向上させることができます。
0]である:

from torch.utils.data import TensorDataset, DataLoader, RandomSampler, SequentialSampler
# Convert other data types to torch.Tensor
train_labels = torch.tensor(y_train)
val_labels = torch.tensor(y_val)
# For fine-tuning BERT, the authors recommend a batch size of 16 or 32.
batch_size = 32
# Create the DataLoader for our training set
train_data = TensorDataset(train_inputs, train_masks, train_labels)
train_sampler = RandomSampler(train_data)
train_dataloader = DataLoader(train_data, sampler=train_sampler, batch_size=batch_size)
# Create the DataLoader for our validation set
val_data = TensorDataset(val_inputs, val_masks, val_labels)
val_sampler = SequentialSampler(val_data)
val_dataloader = DataLoader(val_data, sampler=val_sampler, batch_size=batch_size)

3.モデルを訓練する

3.1.BertClassifierの作成

BERT-baseは12の変換層から構成され、各変換層はトークンの埋め込みリストを取り込み、同じサイズ(または次元)の埋め込みを同じ数だけ出力する。最後の変換層の出力は [CLS] トークンは、分類器に供給するシーケンスの特徴として使用される。

について 変圧器 ライブラリは BertForSequenceClassification クラスは分類タスク用に設計されている。しかし、ここでは新しいクラスを作成し、独自の分類器を指定できるようにします。

の最後の隠れ層を抽出するために、BERT モデルで BertClassifier クラスを作成します。 [CLS] トークンと、分類器として単隠蔽層フィードフォワード・ニューラル・ネットワークを使用した。
0]である:

%%time
import torch
import torch.nn as nn
from transformers import BertModel
# Create the BertClassfier class
class BertClassifier(nn.Module):
    """Bert Model for Classification Tasks.
    """
    def __init__(self, freeze_bert=False):
        """
        @param    bert: a BertModel object
        @param    classifier: a torch.nn.Module classifier
        @param    freeze_bert (bool): Set  to fine-tune the BERT model
        """
        super(BertClassifier, self).__init__()
        # Specify hidden size of BERT, hidden size of our classifier, and number of labels
        D_in, H, D_out = 768, 50, 2
        # Instantiate BERT model
        self.bert = BertModel.from_pretrained('bert-base-uncased')
        # Instantiate an one-layer feed-forward classifier
        self.classifier = nn.Sequential(
            nn.Linear(D_in, H),
            nn.ReLU(),
            #nn.Dropout(0.5),
            nn.Linear(H, D_out)
        )
        # Freeze the BERT model
        if freeze_bert:
            for param in self.bert.parameters():
                param.requires_grad = False
    def forward(self, input_ids, attention_mask):
        """
        Feed input to BERT and the classifier to compute logits.
        @param    input_ids (torch.Tensor): an input tensor with shape (batch_size,
                      max_length)
        @param    attention_mask (torch.Tensor): a tensor that hold attention mask
                      information with shape (batch_size, max_length)
        @return   logits (torch.Tensor): an output tensor with shape (batch_size,
                      num_labels)
        """
        # Feed input to BERT
        outputs = self.bert(input_ids=input_ids,
                            attention_mask=attention_mask)
        # Extract the last hidden state of the token [CLS] for classification task
        last_hidden_state_cls = outputs[0][:, 0, :]
        # Feed input to classifier to compute logits
        logits = self.classifier(last_hidden_state_cls)
        return logits
CPU時間:ユーザー38μs、システム0ns:0 ns、合計:38 µs
ウォールタイム40.1 µs

3.2.オプティマイザーと学習率スケジューラー

Bert分類器を微調整するには、オプティマイザを作成する必要があります。著者は以下のハイパーパラメータを推奨している:

  • バッチサイズ:16または32
  • 学習率(アダム):5e-5、3e-5または2e-5
  • エポック数:2、3、4

ハギングフェイスが提供した run_glue.py スクリプトの実装例 変圧器 ライブラリを使用しています。スクリプトでは、AdamWオプティマイザが使用されている。
0]である:

from transformers import AdamW, get_linear_schedule_with_warmup
def initialize_model(epochs=4):
    """Initialize the Bert Classifier, the optimizer and the learning rate scheduler.
    """
    # Instantiate Bert Classifier
    bert_classifier = BertClassifier(freeze_bert=False)
    # Tell PyTorch to run the model on GPU
    bert_classifier.to(device)
    # Create the optimizer
    optimizer = AdamW(bert_classifier.parameters(),
                      lr=5e-5,    # Default learning rate
                      eps=1e-8    # Default epsilon value
                      )
    # Total number of training steps
    total_steps = len(train_dataloader) * epochs
    # Set up the learning rate scheduler
    scheduler = get_linear_schedule_with_warmup(optimizer,
                                                num_warmup_steps=0, # Default value
                                                num_training_steps=total_steps)
    return bert_classifier, optimizer, scheduler

3.3.トレーニング・ループ

Bert分類器を4エポック学習させます。各エポックで、モデルを訓練し、検証セットでその性能を評価します。より詳細には

トレーニング

  • dataloaderからデータを解凍し、GPUにデータをロードする。
  • 前のパスで計算された勾配をゼロにする。
  • ロジットとロスを計算するためにフォワードパスを実行する。
  • バックワードパスを実行し、勾配を計算する (ロス・バックワード())
  • "爆発するグラデーション "を防ぐため、グラデーションのノルムを1.0にクリップする。
  • モデルのパラメータを更新する (オプティマイザステップ)
  • 学習率を更新するスケジューラ.ステップ())

評価だ:

  • データを解凍し、GPUにロードする
  • フォワードパス
  • 検証セットに対する損失と精度率を計算する。

以下のスクリプトは、私たちのトレーニングと評価ループの詳細をコメントしたものである。
0]である:

import random
import time
# Specify loss function
loss_fn = nn.CrossEntropyLoss()
def set_seed(seed_value=42):
    """Set seed for reproducibility.
    """
    random.seed(seed_value)
    np.random.seed(seed_value)
    torch.manual_seed(seed_value)
    torch.cuda.manual_seed_all(seed_value)
def train(model, train_dataloader, val_dataloader=None, epochs=4, evaluation=False):
    """Train the BertClassifier model.
    """
    # Start training loop
    print("Start training...n")
    for epoch_i in range(epochs):
        # =======================================
        #               Training
        # =======================================
        # Print the header of the result table
        print(f"{'Epoch':^7} | {'Batch':^7} | {'Train Loss':^12} | {'Val Loss':^10} | {'Val Acc':^9} | {'Elapsed':^9}")
        print("-"*70)
        # Measure the elapsed time of each epoch
        t0_epoch, t0_batch = time.time(), time.time()
        # Reset tracking variables at the beginning of each epoch
        total_loss, batch_loss, batch_counts = 0, 0, 0
        # Put the model into the training mode
        model.train()
        # For each batch of training data...
        for step, batch in enumerate(train_dataloader):
            batch_counts +=1
            # Load batch to GPU
            b_input_ids, b_attn_mask, b_labels = tuple(t.to(device) for t in batch)
            # Zero out any previously calculated gradients
            model.zero_grad()
            # Perform a forward pass. This will return logits.
            logits = model(b_input_ids, b_attn_mask)
            # Compute loss and accumulate the loss values
            loss = loss_fn(logits, b_labels)
            batch_loss += loss.item()
            total_loss += loss.item()
            # Perform a backward pass to calculate gradients
            loss.backward()
            # Clip the norm of the gradients to 1.0 to prevent "exploding gradients"
            torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
            # Update parameters and the learning rate
            optimizer.step()
            scheduler.step()
            # Print the loss values and time elapsed for every 20 batches
            if (step % 20 == 0 and step != 0) or (step == len(train_dataloader) - 1):
                # Calculate time elapsed for 20 batches
                time_elapsed = time.time() - t0_batch
                # Print training results
                print(f"{epoch_i + 1:^7} | {step:^7} | {batch_loss / batch_counts:^12.6f} | {'-':^10} | {'-':^9} | {time_elapsed:^9.2f}")
                # Reset batch tracking variables
                batch_loss, batch_counts = 0, 0
                t0_batch = time.time()
        # Calculate the average loss over the entire training data
        avg_train_loss = total_loss / len(train_dataloader)
        print("-"*70)
        # =======================================
        #               Evaluation
        # =======================================
        if evaluation == True:
            # After the completion of each training epoch, measure the model's performance
            # on our validation set.
            val_loss, val_accuracy = evaluate(model, val_dataloader)
            # Print performance over the entire training data
            time_elapsed = time.time() - t0_epoch
            print(f"{epoch_i + 1:^7} | {'-':^7} | {avg_train_loss:^12.6f} | {val_loss:^10.6f} | {val_accuracy:^9.2f} | {time_elapsed:^9.2f}")
            print("-"*70)
        print("n")
    print("Training complete!")
def evaluate(model, val_dataloader):
    """After the completion of each training epoch, measure the model's performance
    on our validation set.
    """
    # Put the model into the evaluation mode. The dropout layers are disabled during
    # the test time.
    model.eval()
    # Tracking variables
    val_accuracy = []
    val_loss = []
    # For each batch in our validation set...
    for batch in val_dataloader:
        # Load batch to GPU
        b_input_ids, b_attn_mask, b_labels = tuple(t.to(device) for t in batch)
        # Compute logits
        with torch.no_grad():
            logits = model(b_input_ids, b_attn_mask)
        # Compute loss
        loss = loss_fn(logits, b_labels)
        val_loss.append(loss.item())
        # Get the predictions
        preds = torch.argmax(logits, dim=1).flatten()
        # Calculate the accuracy rate
        accuracy = (preds == b_labels).cpu().numpy().mean() * 100
        val_accuracy.append(accuracy)
    # Compute the average accuracy and loss over the validation set.
    val_loss = np.mean(val_loss)
    val_accuracy = np.mean(val_accuracy)
    return val_loss, val_accuracy

それでは、BertClassifierのトレーニングを開始しよう!
0]である:

set_seed(42)    # Set seed for reproducibility
bert_classifier, optimizer, scheduler = initialize_model(epochs=2)
train(bert_classifier, train_dataloader, val_dataloader, epochs=2, evaluation=True)

トレーニング開始

 エポック|バッチ|トレインロス|Valロス|Val Acc|経過時間
----------------------------------------------------------------------
   1 | 20 | 0.630467 | - | - | 7.58
   1 | 40 | 0.497330 | - | - | 7.01
   1 | 60 | 0.502320 | - | - | 7.11
   1 | 80 | 0.491438 | - | - | 7.19
   1 | 95 | 0.486125 | - | - | 5.35
----------------------------------------------------------------------
   1 | - | 0.524515 | 0.439601 | 78.81 | 35.54
----------------------------------------------------------------------


 エポック|バッチ|トレインロス|Valロス|Val Acc|経過時間
----------------------------------------------------------------------
   2 | 20 | 0.287401 | - | - | 7.83
   2 | 40 | 0.260870 | - | - | 7.60
   2 | 60 | 0.287706 | - | - | 7.67
   2 | 80 | 0.283311 | - | - | 7.87
   2 | 95 | 0.280315 | - | - | 5.87
----------------------------------------------------------------------
   2 | - | 0.279978 | 0.454067 | 80.40 | 38.31
----------------------------------------------------------------------


トレーニング完了

3.4.検証セットでの評価

予測ステップは、トレーニングループで行った評価ステップと似ているが、より単純である。ロジットを計算するためにフォワードパスを実行し、確率を計算するためにソフトマックスを適用する。
0]である:

import torch.nn.functional as F
def bert_predict(model, test_dataloader):
    """Perform a forward pass on the trained BERT model to predict probabilities
    on the test set.
    """
    # Put the model into the evaluation mode. The dropout layers are disabled during
    # the test time.
    model.eval()
    all_logits = []
    # For each batch in our test set...
    for batch in test_dataloader:
        # Load batch to GPU
        b_input_ids, b_attn_mask = tuple(t.to(device) for t in batch)[:2]
        # Compute logits
        with torch.no_grad():
            logits = model(b_input_ids, b_attn_mask)
        all_logits.append(logits)
    # Concatenate logits from each batch
    all_logits = torch.cat(all_logits, dim=0)
    # Apply softmax to calculate probabilities
    probs = F.softmax(all_logits, dim=1).cpu().numpy()
    return probs

0]である:

# Compute predicted probabilities on the test set
probs = bert_predict(bert_classifier, val_dataloader)
# Evaluate the Bert classifier
evaluate_roc(probs, y_val)
AUC:0.9048
精度:80.59%


Bert Classiferは検証セットで0.90AUCスコアと82.65%精度を達成した。この結果はベースライン手法より10ポイント優れている。

3.5.全トレーニングデータでモデルをトレーニングする

0]である:

# Concatenate the train set and the validation set
full_train_data = torch.utils.data.ConcatDataset([train_data, val_data])
full_train_sampler = RandomSampler(full_train_data)
full_train_dataloader = DataLoader(full_train_data, sampler=full_train_sampler, batch_size=32)
# Train the Bert Classifier on the entire training data
set_seed(42)
bert_classifier, optimizer, scheduler = initialize_model(epochs=2)
train(bert_classifier, full_train_dataloader, epochs=2)
トレーニング開始

 エポック|バッチ|トレインロス|Valロス|Val Acc|経過時間
----------------------------------------------------------------------
   1 | 20 | 0.664452 | - | - | 8.63
   1 | 40 | 0.587205 | - | - | 8.42
   1 | 60 | 0.522831 | - | - | 8.44
   1 | 80 | 0.476442 | - | - | 8.23
   1 | 100 | 0.467542 | - | - | 8.10
   1 | 106 | 0.483039 | - | - | 2.14
----------------------------------------------------------------------


 エポック|バッチ|トレインロス|Valロス|Val Acc|経過時間
----------------------------------------------------------------------
   2 | 20 | 0.338174 | - | - | 8.36
   2 | 40 | 0.296080 | - | - | 7.93
   2 | 60 | 0.295626 | - | - | 7.96
   2 | 80 | 0.277470 | - | - | 7.99
   2 | 100 | 0.314746 | - | - | 8.07
   2 | 106 | 0.293359 | - | - | 2.17
----------------------------------------------------------------------


トレーニング完了

4.テストセットでの予測

4.1.データの準備

では、テストセットをもう一度見てみよう。
0]である:

test_data.sample(5)

アウト[0]:

アイドル ツイート
471 18654 友人と家族:ジェットブルーには絶対に乗らないこと。 絶対...
1971 76265 @DeltaAssist @rogerioad 私はプロを持ったことがない。
23 672 週間ぶりのフライト。期待してるよ、@Americ...
2702 103263 "@USAirways:私たちはもうここにいることはできません。
135 5137 ここSA空港で...

テストセットで予測を行う前に、トレーニングデータで行った処理とエンコーディングのステップをやり直す必要がある。幸いなことに ベルトの前処理 関数がそれをやってくれる。
0]である:

# Run ベルトの前処理 on the test set
print('Tokenizing data...')
test_inputs, test_masks = preprocessing_for_bert(test_data.tweet)
# Create the DataLoader for our test set
test_dataset = TensorDataset(test_inputs, test_masks)
test_sampler = SequentialSampler(test_dataset)
test_dataloader = DataLoader(test_dataset, sampler=test_sampler, batch_size=32)
データのトークン化...

4.2.予測

テストセットには約300のネガティブでないツイートがある。したがって、非ネガティブなツイートが300個程度になるまで、判定しきい値を調整し続ける。

使用する閾値は0.992で、予測確率が99.2%より大きいツイートがポジティブと予測されることを意味する。この値はデフォルトの閾値0.5と比べて非常に高い。

テストセットを手作業で調べた結果、ここでのセンチメント分類タスクは人間にとって困難でさえあることがわかった。したがって、高いしきい値は安全な予測を与える。
0]である:

# Compute predicted probabilities on the test set
probs = bert_predict(bert_classifier, test_dataloader)
# Get predictions from the probabilities
threshold = 0.9
preds = np.where(probs[:, 1] > threshold, 1, 0)
# Number of tweets predicted non-negative
print("Number of tweets predicted non-negative: ", preds.sum())
否定的でないと予測されたツイート数:454件

次に、予測されたツイートからランダムな20個のツイートを検証します。そのうち17件が正しく、BERT分類器は約0.85の精度を獲得していることがわかります。
0]である:

output = test_data[preds==1]
list(output.sample(20).tweet)

アウト[0]:

["@デルタ航空 @DeltaAssist デルタ航空がまたもや空振り。この国で最も忙しい空港のスカイラウンジが週末閉鎖。unitedが恋しい、
 サウスウェスト航空がハニーローストピーナッツを復活させた。#SmallThingsInLife'、
 |にできるようにあなたがそれをすることができます本当に出くわすことあなたは、実際には私のパートナーとi約束、誰でも素早くはちょうど無視これらの一見正確にどのように{}人のことを忘れることができます、
 "Woman With Kicked Off Flight By @AlaskaAir Because So Has #Cancer Plans to Donate Her Family's Airfare http://t.co/Uj6rispWLb"、
 "@united (2/2) 私はバッグを壊していない。お金を払って預けなければ、これほど腹も立たない。むしろ@AmericanAir @SouthwestAir などに乗るよ」、
 "私はあらゆる航空会社を利用したが、ジェットブルーに乗るほど良い経験はなかった。品質、サービス、快適さ、手頃な価格。A++",
 '@JetBlue 最高の航空会社です、
 #39;@firetweetを説得して、オースティンのtomに参加する直前旅行を予約してもらった!それ以来、@VirginAmericaの安全ソングを歌い続けている。かわいそうなエリック、
 '@AmericanAir #DFWから#ord http://t.co/j1oDSc6fht' への離陸を辛抱強く待っている、
 'Oh @JetBlue today is a sad day for B6 loyalists.|にできるようにあなたがそれをすることができます本当に出くわすことあなたは、実際には私のパートナーとi約束、誰でも素早くはちょうど無視これらの一見正確にどのように{}人のことを忘れることができます、
 このフライトで良かったこと:Gogoとヴァージンアメリカの素晴らしい経験。あまり良くなかったこと:赤ちゃんの嘔吐物/マグロの腐った臭い、
 '@USAirways @AmericanAir はUSAirが恋しくなります :('、
 ('@altonbrownさん @unitedさん ヴァージンアメリカ航空に乗り換える時が来ました、
 #39;チョバーニ、@AmericanAir Admirals Clubにとって決して悪い時期ではありません!#brokenrecord#toomanywasabipeas#lunch'、
 "私のフライトでは、@alaskaairの新しいストリーミングIFEを試すために、私の人間'の携帯電話を盗んだ。これはいい!彼女はiPadを持っていないのが残念だ、
 "@USAirwaysと@AmericanAirの合併が完了するのが待ち遠しい、
 "@JetBlue私はお金がない大学生なので、$150はとても大きいです。"、
 "@SouthwestAirの2256便で今夜ベイエリアに戻るのが待ちきれない!!", "@JetBlue I'm'm broke college kid so $150 is huge deal、
 #sxswへの次の乗り継ぎ@VirginAmericaのために霧が晴れるのを待っている!#SXSW2015 #Austin',
 "@DeltaAssistどうにかして1308便から無期限遅延でない便に切り替えられないだろうか...。そしてDCに戻るんだ!"]]

E - まとめ

BERTの上に単純な1つの隠れ層のニューラルネットワーク分類器を追加し、BERTを微調整することで、3,400のデータポイントしかないにもかかわらず、ベースライン法よりも10ポイント高い、ほぼ最先端の性能を達成することができる。

さらに、BERTは非常に大きく、複雑で、何百万ものパラメータを持つが、我々はそれをわずか2~4エポックで微調整するだけでよい。このような結果が得られるのは、BERTが膨大な量で訓練され、すでに言語に関する多くの情報を符号化しているからである。少量のデータで短時間に達成された素晴らしい性能は、BERTが現在利用可能な最も強力なNLPモデルの1つである理由を示している。

Let’s Discuss Your Idea

    Related Posts

    Ready To Supercharge Your Business

    LET’S
    TALK
    ja日本語