
Contents
- 1 はじめに
- 2 BERTの仕組み
- 3 インスタンスへのログイン
- 4 データセットとコードの準備
- 5 Jupyterへのログイン
- 6 BERT APIの利用方法
- 7 BERTが受け取れる形式にデータ整形
- 8 トレーニング及び分類予測
- 9 まとめ
はじめに
BERT(1)はさまざまな自然言語処理タスクでSOTA(2)を達成しているDeepLearningモデルです。この記事はKaggleで掲載されているLearning BERT for the first timeにならってBERTの使い方を示します。
*1 Bidirectional Encoder Representations for Transformersの略。 2018年10月にGoogleのJacob Devlinらの論文で発表された自然言語処理モデル。日本語では「Transformerによる双方向のエンコード表現」と訳されている。BERTではTransformerというアーキテクチャで文章を文頭・文末の双方向から学習することで、文脈を読めるようになった。
*2 State-of-the-Artの略称。ある特定の専門技術領域において現時点での最先端レベルの性能(=機械学習では正解率などのスコア/精度)を達成していることを表す。
BERTの仕組み
これまでの一般的な自然言語処理モデルは文章を単一方向からしか処理することができませんでした。しかし、BERTは双方向のエンコード表現ができます。BERTにはMasked Language ModelとNext Sentence Predictionという2つの手法を同時に進行していき、学習できる仕組みです。Masked Language Modelで文章の文頭及び文末の双方向から学習していくことができます。一方Next Sentence Predictionでは二つの入力された文に対し、その二つの文が隣り合っているのかを当てるように学習します。詳細については参考記事1~5をご参照ください。それでは早速BERTの関連のnotebookを実行してみましょう。このnotebookではBidirectional Encoder Representations from Transformers (BERT, bert_en_uncased_L-12_H-768_A-12)というAPIをどのようにアクセス(wrap)するかを説明します。
インスタンスへのログイン
早速インスタンスにログインをしましょう。ターミナルから以下のコマンドを入力して、アクセスサーバへ接続をしてください(*ここではインスタンスのIPアドレスは10.233.101.21とします)。下図のように表示されれば接続完了です。
ssh -L 20122:10.233.101.21:22 user@[アクセスサーバIP] -p 30022 -i .\.ssh\ackey.txt

続いて新規にターミナルを立ち上げて、下記のコマンドを入力してください。下図のように表示されれば接続完了です。
ssh -L 8888:localhost:8888 user@localhost -p 20122 -i .ssh/mykey.txt

データセットとコードの準備
今回使用するデータセットおよびソースコードをgithubからダウンロードします。
gitがインストールされていない場合、下記コマンドにてインストールしてください。
sudo apt -y update
sudo apt -y install git
続いて下記コマンドにて必要なデータ類をダウンロードします。
git clone https://github.com/highreso/bert.git
Jupyterへのログイン
インスタンスに接続されたターミナルにて、下記のコマンドを実行してjupyter labを起動してください。
jupyter lab --ip=* --no-browser

続いてローカルPCにてブラウザを立ち上げて、"http://localhost:8888" にアクセスしてください。以降はjupyterへのログインが成功した前提で、各セルで実行するコードの解説をいたします。

ここからは先ほどgithubから取得した、bertフォルダ内のjp-learning-bert-for-the-first-time.ipynbの各セルについて、順に内容を確認しながらセルを実行していきましょう。

まずは今回はtensorflow環境を使用するため、Environmentを”conda_tensorflow24_py36″に設定します。

最初にGPUがtensorflowで使用可能か確認します。
In [1]:
## Check GPU recognized from tensorflow.python.client import device_lib device_lib.list_local_devices()
Out[1]:
[name: “/device:CPU:0”
device_type: “CPU”
memory_limit: 268435456
locality {
}
incarnation: 1029349297547213705,
name: “/device:GPU:0”
device_type: “GPU”
memory_limit: 39395347712
locality {
bus_id: 1
links {
}
}
incarnation: 11126425406798181006
physical_device_desc: “device: 0, name: A100-PCIE-40GB, pci bus id: 0000:c1:00.0, compute capability:
8.0″]
device_type: “GPU” が表示されていればGPUが認識されている状態です。
続いて、必要なライブラリをインストールします。
In [2]:
!pip install -U pandas==1.1.5 !pip install -U tensorflow_hub==0.12.0 !pip install -U bert-tensorflow==1.0.1 import sys sys.path.append('/home/user/.local/lib/python3.6/site-packages') import numpy as np import pandas as pd import re import tensorflow as tf from tensorflow.keras.layers import Dense, Input, Dropout from tensorflow.keras.optimizers import Adam from tensorflow.keras.models import Model from tensorflow.keras.callbacks import ModelCheckpoint import tensorflow_hub as hub from bert import tokenization
pd.read.scv(“./input/train.csv”) を使って、inputフォルダにある学習用データtrain.csvファイルを読み込みます。読み込んだデータの先頭5行分のデータをtrain.head()というコマンドで、リストにします。
In [3]:
train =pd.read_csv("./input/train.csv") train.head()
Out[3]:
id | keyword | location | text | target | |
---|---|---|---|---|---|
0 | 1 | NaN | NaN | Our Deeds are the Reason of this #earthquake M… | 1 |
1 | 4 | NaN | NaN | Forest fire near La Ronge Sask. Canada | 1 |
2 | 5 | NaN | NaN | All residents asked to ‘shelter in place’ are … | 1 |
3 | 6 | NaN | NaN | 13,000 people receive #wildfires evacuation or… | 1 |
4 | 7 | NaN | NaN | Just got sent this photo from Ruby #Alaska as … | 1 |
pd.read.scv(“./input/test.csv”) を使って、inputフォルダにあるテスト用データtest.csvファイルを読み込みます。読み込んだデータの先頭5行分のデータをtest.head()というコマンドで、リストにします。
In [4]:
test =pd.read_csv("./input/test.csv") test.head()
Out[4]:
id | keyword | location | text | |
---|---|---|---|---|
0 | 0 | NaN | NaN | Just happened a terrible car crash |
1 | 2 | NaN | NaN | Heard about #earthquake is different cities, s… |
2 | 3 | NaN | NaN | there is a forest fire at spot pond, geese are… |
3 | 9 | NaN | NaN | Apocalypse lighting. #Spokane #wildfires |
4 | 11 | NaN | NaN | Typhoon Soudelor kills 28 in China and Taiwan |
BERT APIの利用方法
下記のやり方で事前にトレーニングされたBERTエンコーダーとプリプロセッサーを利用することができます。このトレーニングされたモデルを利用することで、簡単に単語の分類予測等を実施することができます。%%timeは実行時間を計測するためのコマンドになります。
In [5]:
%%time module_url = "https://tfhub.dev/tensorflow/bert_en_uncased_L-12_H-768_A-12/1" bert_layer = hub.KerasLayer(module_url, trainable=True)
CPU times: user 5.24 s, sys: 735 ms, total: 5.98 s
Wall time: 5.97 s
その後、取得したBERTのレイヤーを下記のようにトークン化します。
In [6]:
tf.gfile = tf.io.gfile vocab_file = bert_layer.resolved_object.vocab_file.asset_path.numpy() do_lower_case = bert_layer.resolved_object.do_lower_case.numpy() tokenizer = tokenization.FullTokenizer(vocab_file, do_lower_case)
BERTが受け取れる形式にデータ整形
ここは前処理の基本的な考え方を説明します。
In [7]:
text = "This is a Goat, and I am riding a Boat...." tokenize_ = tokenizer.tokenize(text) print("Text after tokenization: ") print(tokenize_) max_len = 25 text = tokenize_[:max_len-2] input_sequence = ["[CLS]"] + text + ["[SEP]"] pad_len = max_len - len(input_sequence) print("After adding [CLS] and [SEP]: ") print(input_sequence) tokens = tokenizer.convert_tokens_to_ids(input_sequence) print("After converting Tokens to Id: ") print(tokens) tokens += [0] * pad_len print("tokens: ") print(tokens) pad_masks = [1] * len(input_sequence) + [0] * pad_len print("Pad Masking: ") print(pad_masks) segment_ids = [0] * max_len print("Segment Ids: ") print(segment_ids) Copy
Text after tokenization:
[‘this’, ‘is’, ‘a’, ‘goat’, ‘,’, ‘and’, ‘i’,’am’, ‘riding’, ‘a’, ‘boat’, ‘.’, ‘.’, ‘.’,’.’]
After adding [CLS] and [SEP]:
[‘[CLS]’, ‘this’, ‘is’, ‘a’, ‘goat’, ‘,’, ‘and’,’i’, ‘am’, ‘riding’, ‘a’, ‘boat’, ‘.’, ‘.’,’.’, ‘.’, ‘[SEP]’]
After converting Tokens to Id:
[101, 2023, 2003, 1037, 13555, 1010, 1998, 1045, 2572, 5559, 1037, 4049, 1012, 1012, 1012, 1012, 102]
tokens:
[101, 2023, 2003, 1037, 13555, 1010, 1998, 1045, 2572, 5559, 1037, 4049, 1012, 1012, 1012, 1012, 102, 0, 0, 0,0, 0, 0, 0, 0]
Pad Masking:
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]
Segment Ids:
[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]
上記のコードではデータの前処理(BERTが受け取れる形式にデータを整形)します。まずは下記のようにBERT Tokenizerを用いて文章をトークン化(単語分割)します。
[‘this’, ‘is’, ‘a’, ‘goat’, ‘,’, ‘and’, ‘i’,’am’, ‘riding’, ‘a’, ‘boat’, ‘.’, ‘.’, ‘.’,’.’]
その後、文章の最初及び最後に、Special tokenの[CLS]と[SEP]を追加します。
[‘[CLS]’, ‘this’, ‘is’, ‘a’, ‘goat’, ‘,’, ‘and’,’i’, ‘am’, ‘riding’, ‘a’, ‘boat’, ‘.’, ‘.’,’.’, ‘.’, ‘[SEP]’]
その次はトークン化(単語分割)したトークンを下記のようにID化します。
[101, 2023, 2003, 1037, 13555, 1010, 1998, 1045, 2572, 5559, 1037, 4049, 1012, 1012, 1012, 1012, 102]
BERTが処理できるように、文章の長さが固定されているため、文章あたりの最大単語数に合わせて、ゼロで”文章”の長さを合わせています。合わせたIDは下記の通りになります。これはPaddingというプロセスといいます。指定した長さに満たない文章を[Pad]という意味を持たない単語の埋める処理を行うことです。今回は長すぎるプロセスがないですが、ある場合、[Truncating]で指定した長さを超える単語を切り捨てることです。
[101, 2023, 2003, 1037, 13555, 1010, 1998, 1045, 2572, 5559, 1037, 4049, 1012, 1012, 1012, 1012, 102, 0, 0, 0,0, 0, 0, 0, 0]
上述の考えに基づき、BERTが受け取れる形式にデータを整形する関数を下記に示します。
In [8]:
def pre_Process_data(documents, tokenizer, max_len=512): ''' For preprocessing we have regularized, transformed each upper case into lower case, tokenized, Normalized and remove stopwords. For normalization, we have used PorterStemmer. Porter stemmer transforms a sentence from this "love loving loved" to this "love love love" ''' all_tokens = [] all_masks = [] all_segments = [] print("Pre-Processing the Data.........\n") for data in documents: review = re.sub('[^a-zA-Z]', ' ', data) url = re.compile(r'https?://\S+|www\.\S+') review = url.sub(r'',review) html=re.compile(r'<.*?>') review = html.sub(r'',review) emoji_pattern = re.compile("[" u"\U0001F600-\U0001F64F" # emoticons u"\U0001F300-\U0001F5FF" # symbols & pictographs u"\U0001F680-\U0001F6FF" # transport & map symbols u"\U0001F1E0-\U0001F1FF" # flags (iOS) u"\U00002702-\U000027B0" u"\U000024C2-\U0001F251" "]+", flags=re.UNICODE) review = emoji_pattern.sub(r'',review) text = tokenizer.tokenize(review) text = text[:max_len-2] input_sequence = ["[CLS]"] + text + ["[SEP]"] pad_len = max_len - len(input_sequence) tokens = tokenizer.convert_tokens_to_ids(input_sequence) tokens += [0] * pad_len pad_masks = [1] * len(input_sequence) + [0] * pad_len segment_ids = [0] * max_len all_tokens.append(tokens) all_masks.append(pad_masks) all_segments.append(segment_ids) return np.array(all_tokens), np.array(all_masks), np.array(all_segments)
In [9]:
input_word_id = Input(shape=(max_len,),dtype=tf.int32, name="input_word_ids") input_mask = Input(shape=(max_len,), dtype=tf.int32, name="input_mask") segment_id = Input(shape=(max_len,), dtype=tf.int32, name = "segment_id") _, sequence_output = bert_layer([input_word_id, input_mask, segment_id]) clf_output = sequence_output[:, 0, :] model = Model(inputs=[input_word_id, input_mask, segment_id],outputs=clf_output) model.compile(Adam(lr=2e-5), loss='binary_crossentropy', metrics=['accuracy']) model.summary() print("shape of _ layer of BERT: "+str(_.shape)) print("shape of last layer of BERT: "+str(sequence_output.shape))
Model: "model"
Layer (type) Output Shape Param # Connected to
==================================================================================================
input_word_ids (InputLayer) [(None, 25)] 0
input_mask (InputLayer) [(None, 25)] 0
segment_id (InputLayer) [(None, 25)] 0
__
keras_layer (KerasLayer) [(None, 768), (None, 109482241 input_word_ids[0][0]
input_mask[0][0]
segment_id[0][0]
tf.operators.getitem (Slici (None, 768) 0 keraslayer[0][1]
==================================================================================================
Total params: 109,482,241
Trainable params: 109,482,240
Non-trainable params: 1
shape of layer of BERT: (None, 768)
shape of last layer of BERT: (None, None, 768)
In [10]:
def build_model(bert_layer, max_len=512): input_word_id = Input(shape=(max_len,),dtype=tf.int32, name="input_word_ids") input_mask = Input(shape=(max_len,), dtype=tf.int32, name="input_mask") segment_id = Input(shape=(max_len,), dtype=tf.int32, name = "segment_id") _, sequence_output = bert_layer([input_word_id, input_mask, segment_id]) clf_output = sequence_output[:, 0, :] dense_layer1 = Dense(units=256,activation='relu')(clf_output) dense_layer1 = Dropout(0.4)(dense_layer1) dense_layer2 = Dense(units=128, activation='relu')(dense_layer1) dense_layer2 = Dropout(0.4)(dense_layer2) out = Dense(1, activation='sigmoid')(dense_layer2) model = Model(inputs=[input_word_id, input_mask, segment_id],outputs=out) model.compile(Adam(lr=2e-5), loss='binary_crossentropy', metrics=['accuracy']) return model
In [11]:
train_input = pre_Process_data(train.text.values, tokenizer, max_len=260) test_input = pre_Process_data(test.text.values, tokenizer, max_len=260) train_labels = train.target.values
Pre-Processing the Data………
Pre-Processing the Data………
データセットを前処理した後で、下記のコードで、モデルにロードします。
In [12]:
model = build_model(bert_layer, max_len=260) model.summary()
Model: “model_1”
Layer (type) Output Shape Param # Connected to
==================================================================================================
input_word_ids (InputLayer) [(None, 260)] 0
input_mask (InputLayer) [(None, 260)] 0
segment_id (InputLayer) [(None, 260)] 0
__
keras_layer (KerasLayer) [(None, 768), (None, 109482241 input_word_ids[0][0]
input_mask[0][0]
segment_id[0][0]
tf.operators.getitem_1 (Sli (None, 768) 0 keras_layer[1][1]
dense (Dense) (None, 256) 196864 tf.operators.getitem_1[0][0]
dropout (Dropout) (None, 256) 0 dense[0][0]
dense_1 (Dense) (None, 128) 32896 dropout[0][0]
dropout_1 (Dropout) (None, 128) 0 dense_1[0][0]
dense_2 (Dense) (None, 1) 129 dropout_1[0][0]
==================================================================================================
Total params: 109,712,130
Trainable params: 109,712,129
Non-trainable params: 1
トレーニング及び分類予測
ここではモデルのファインチューニングをするために、トレーニングを行います。train.csvファイルのデータは既にtrain_inputに入れてあるので、model.fitの関数(厳密にはfit関数)を利用してモデルのトレーニングを行います。
In [13]:
checkpoint = ModelCheckpoint('model.h5', monitor='val_loss', save_best_only=True) train_history = model.fit( train_input, train_labels, validation_split=0.2, epochs=10, callbacks=[checkpoint], # batch_size=32 batch_size=2 )
Epoch 1/10
3045/3045 [==============================] – 105s 30ms/step – loss: 0.5305 – accuracy: 0.7514 – val_loss:
0.4145 – val_accuracy: 0.8253
Epoch 2/10
3045/3045 [==============================] – 90s 30ms/step – loss: 0.3250 – accuracy: 0.8813 – val_loss: 0.4846
– val_accuracy: 0.8201
Epoch 3/10
3045/3045 [==============================] – 93s 30ms/step – loss: 0.1672 – accuracy: 0.9417 – val_loss: 0.6379
– val_accuracy: 0.7873
Epoch 4/10
3045/3045 [==============================] – 96s 31ms/step – loss: 0.0861 – accuracy: 0.9693 – val_loss: 0.7866
– val_accuracy: 0.7951
Epoch 5/10
3045/3045 [==============================] – 96s 32ms/step – loss: 0.0876 – accuracy: 0.9682 – val_loss: 0.8580
– val_accuracy: 0.8201
Epoch 6/10
3045/3045 [==============================] – 96s 32ms/step – loss: 0.0654 – accuracy: 0.9771 – val_loss: 0.8860
– val_accuracy: 0.7932
Epoch 7/10
3045/3045 [==============================] – 96s 32ms/step – loss: 0.0764 – accuracy: 0.9759 – val_loss: 0.8610
– val_accuracy: 0.8070
Epoch 8/10
3045/3045 [==============================] – 96s 32ms/step – loss: 0.0549 – accuracy: 0.9782 – val_loss: 0.7016
– val_accuracy: 0.8050
Epoch 9/10
3045/3045 [==============================] – 96s 32ms/step – loss: 0.0523 – accuracy: 0.9783 – val_loss: 1.0104
– val_accuracy: 0.8024
Epoch 10/10
3045/3045 [==============================] – 96s 32ms/step – loss: 0.0465 – accuracy: 0.9778 – val_loss: 1.1859
– val_accuracy: 0.8162
In [14]:
submission = pd.read_csv("./input/sample_submission.csv") submission.head()
Out[14]:
id | target | |
---|---|---|
0 | 0 | 0 |
1 | 2 | 0 |
2 | 3 | 0 |
3 | 9 | 0 |
4 | 11 | 0 |
ここでは予め準備したtest.csvのテストデータを用いて下記のコードでモデルの確度をテストすることができます。
test_pred = model.predict(test_input)In [15]:
model.load_weights('model.h5') test_pred = model.predict(test_input) test_pred
Out[15]:
array([[0.95257264],
[0.9160163 ],
[0.9756269 ],
…,
[0.9794409 ],
[0.92635137],
[0.5679488 ]], dtype=float32)
まとめ
モデルの訓練が終われば、model.predict()という関数で、そのモデルを使って分類予測を実施することができます。arrayに入っている数字は「確信度」を表しています。上述のように、BERTの使い方を説明しました。
参考記事1:BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding
参考記事2:Paper : BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding
参考記事6:自然言語処理モデル(BERT)を利用した日本語の文章分類 〜GoogleColab & Pytorchによるファインチューニング〜
