概要
個人的な備忘録を兼ねたPyTorchの基本的な解説とまとめです。BERTタイプのMLM事前学習に向けて学んだことをまとめおきたいと思います。Transformer Encoderタイプ(BERTタイプ)の事前学習で利用されるMLM (Masked Language Modeling) の方法を実装、事前学習してみるのが目的となります。
自作のモデルでも事前学習の結果<mask>部分が予測できることを確かめたい 🔥
- マスク化とミニバッチ(1回目)
- BERTタイプのMLM学習用モデルと重み共有(2回目)
- 実際に事前学習(3回目・今回)
- 自作事前学習モデルでファインチューニング(4回目)
- Datasetクラスの改良(5回目)
方針
- できるだけ同じコード進行
- できるだけ簡潔(細かい内容は割愛)
演習用のファイル
- トークナイザー: unigram_tokenizer_2k.json
- 学習データ:train_data_unigram_2k.jsonl
- 保存モデル:unigram_2k.model
- コード: sample_31.ipynb
1. 学習データとトークナイザーの作成
wikipediaの日本の歴史に関する内容(縄文時代〜平成までの概要)を利用しました。47万字程度のとても小さなコーパスです。演習の学習データ (train_data_unigram_2k.jsonl) にはすでにトークナイズされたテキストとIDベクトルが保存されています。
1.1 前処理
wikipediaから取得したテキストデータに対して、3種類の加工をしました。前処理と言っても大したことはやっていません![]()
- 「##」マークの削除
- 文頭にある不要な空白の削除(余分な空行もなくなります)
- 改行も削除(1行になります)
文字の正規化など本当は行ったほうが、もちろん良いですよ🐾
1.2 トークナイザーの作成
収集したデータを使ってトークナイザーも作成します。
- データ量が少ないので語彙数を2000に固定する形で作成
- <mask>部分の穴埋め効果を見るために、ByteLevelは使わない。バイト列が予測できても見た目が面白くないですしね🌵
- BPE、WordPiece、Unigram LMのどれでもOKですが、今回は Unigram LMで試してみました。どのタイプでも<mask>予測に関しては、似通った結果になります1。
1.3 学習データの作成
TensorDatasetとDataLoaderを組み合わせるタイプなので、入力するデータの系列長を等しくする必要があります。1行にしたデータに対して次の作業を行い最終的な学習データとしました2。異なる長さのままデータを扱う方法は「5回目のDatasetクラスカスタマイズ」で挑戦してみたいと思います![]()
- トークナイザーを利用してIDに変換。
- 文頭に<bos>のID、文末に<eos>のIDを追加して、系列長が64になるように、文書を順番に区切る。系列長が64未満の場合(おそらく最終行のみ)は<pad>のIDを追加して長さを揃える。
10個区切りのでの例
表のtextのように、8トークンずつ文章を先頭から区切ります。文頭に<bos>のID、文末に<eos>のIDを挿入して、系列長が10個になるようにidsを作成していきます。最後尾のみ10トークンに満たない場合、<pad>を挿入します。
| ids | text | |
|---|---|---|
| 0 | [1, 314, 36, 107, 5, 302, 1784, 12, 1293, 2] | [鎌倉時代, は、, 京都, の, 朝, 廷, と, 並] |
| 1 | [1, 544, 577, 1282, 23, 1864, 263, 9, 1942, 2] | [んで, 相, 模, 国, 鎌, 倉, に, 置] |
| 2 | [1, 62, 461, 446, 10, 355, 23, 471, 296, 2] | [か, れた, 鎌倉幕府, が, 全, 国, 統, 治] |
| 3 | [1, 5, 44, 587, 27, 6, 129, 558, 54, 2] | [の, 中, 心, である, 。, 日本, 史, 上] |
| 4 | [1, 14, 1154, 66, 471, 296, 10, 759, 93, 2] | [で, 本格的な武家政権, による, 統, 治, が, 初, め] |
| 5 | [1, 33, 137, 213, 461, 6, 2, 0, 0, 0] | [て, 行, わ, れた, 。] |
文章を先頭から順番に区切るだけです。どのようなデータや形でトークナイズや学習データを作成するのかは、細かいけど重要そうなポイントっぽい。たしか「ゴミを入れればゴミが出力」だったかな?精度の良いデータに関しては今後のエクササイズにしよう
3 奥が深そうな予感がする。
2. 事前学習
いよいよ事前学習。PyTorchによるプログラムの流れを確認します。基本的に下記の5つの流れで、初回から全く変わっていません。
- データの読み込みとtorchテンソルへの変換 (2.1)
- ネットワークモデルの定義と作成 (2.2)
- 誤差関数と誤差最小化の手法の選択 (2.3)
- 変数更新のループ (2.4)
- モデルの保存 (2.5)
2.1 データの読み込みとtorchテンソルへの変換
毎回おなじみのライブラリーを色々読み込む部分となります。
import torch
import torch.nn as nn
import pandas as pd # JSONLデータ読み込み時利用
import random # mask化で利用 mlm_sample関数
from torch.utils.data import DataLoader, TensorDataset
from tokenizers import Tokenizer
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
続いて、学習データの読み込みです。pandasを利用して、JSONL形式を読み込みます。
data_filename = "./data/train_data_unigram_2k.jsonl"
df = pd.read_json(data_filename, lines=True)
x = torch.LongTensor(df["ids"])
説明メモ
-
x = torch.LongTensor(df["ids"]): 学習に利用するIDベクトル。実際に学習に使うのはxをマスク化したものとなります。
2.2 ネットワークモデルの定義と作成
モデルは前回構築したTransformer Encoder (BERTタイプ)の ネットワークモデルを利用します。トークナイザーを準備して、モデル設定クラスを利用する形です。
ModelConfigクラスで基本的な数値の設定を行い、DNNクラスに図1のネットワーク構造を記述するだけです。
pretrain_filename = "./model/unigram_2k.model" # 学習したモデルを保存するファイル名
tokenizer_filename = "./tokenizer/unigram_tokenizer_2k.json" # 利用するトークナイザー
# (1) tokenizerの読み込み
tokenizer = Tokenizer.from_file(tokenizer_filename)
# (2) モデルの設定を司るクラス
class ModelConfig:
def __init__(self, tokenizer):
# モデル構造
self.vocab_size = tokenizer.get_vocab_size()
self.d_model = 64
self.seq_len = 64
self.nhead = 4
self.dim_feedforward = 256
self.num_layers = 6
self.dropout = 0.1
# 特殊トークンID
self.pad_token_id = tokenizer.token_to_id("<pad>")
self.mask_token_id = tokenizer.token_to_id("<mask>")
self.bos_token_id = tokenizer.token_to_id("<bos>")
self.eos_token_id = tokenizer.token_to_id("<eos>")
self.unk_token_id = tokenizer.token_to_id("<unk>")
# 特殊トークンのセット
self.special_tokens_set = {
self.pad_token_id,
self.mask_token_id,
self.bos_token_id,
self.eos_token_id,
self.unk_token_id,
}
# 通常トークンのリスト special_tokenを除くトークンのリスト(MLMランダム置換用)
self.normal_tokens_list = [
i for i in range(self.vocab_size)
if i not in self.special_tokens_set
]
# 学習設定 (今回は利用しないけど使うと便利かも)
self.batch_size = 32
self.learning_rate = 1e-4
self.num_epochs = 1000
self.mask_prob = 0.15
self.max_grad_norm = 1.0
# (3) MLM用の出力部分のネットワークブロック。重み共有手法でパラメータを削減しつつ学習効率アップ
class MLMHead(nn.Module):
def __init__(self, config: ModelConfig, embedding_weight):
super().__init__()
self.fc = nn.Linear(config.d_model, config.d_model)
self.act = nn.GELU()
self.ln = nn.LayerNorm(config.d_model)
self.classification_head = nn.Linear(config.d_model, config.vocab_size, bias=False)
self.classification_head.weight = embedding_weight # weight tying
self.bias = nn.Parameter(torch.zeros(config.vocab_size))
def forward(self, x):
x = self.fc(x)
x = self.act(x)
x = self.ln(x)
x = self.classification_head(x) + self.bias
return x
# (4) 実際に利用するネットワーク
class DNN(nn.Module):
def __init__(self, config: ModelConfig):
super().__init__()
self.config = config
# 埋め込み層
self.token_embedding = nn.Embedding(
num_embeddings=config.vocab_size,
embedding_dim=config.d_model,
padding_idx=config.pad_token_id
)
self.pos_embedding = nn.Embedding(num_embeddings=config.seq_len, embedding_dim=config.d_model)
self.layer_norm = nn.LayerNorm(config.d_model)
self.dropout = nn.Dropout(config.dropout)
# Transformer Encoder
encoder_layer = nn.TransformerEncoderLayer(
d_model=config.d_model,
nhead=config.nhead,
dim_feedforward=config.dim_feedforward,
dropout=config.dropout,
batch_first=True,
)
self.transformer_encoder = nn.TransformerEncoder(encoder_layer, num_layers=config.num_layers, enable_nested_tensor=False)
# MLM用の出力層:BERT風レイヤー 各トークン位置で語彙全体を予測
self.mlm_head = MLMHead(config, self.token_embedding.weight)
#self.mlm_head = nn.Linear(config.d_model, config.vocab_size)
def forward(self, x):
# マスクの作成
src_key_padding_mask = (x == self.config.pad_token_id)
# 埋め込み
tok_emb = self.token_embedding(x)
pos_emb = self.pos_embedding(torch.arange(x.size(1), device=x.device))
x = tok_emb + pos_emb.unsqueeze(0)
x = self.layer_norm(x)
x = self.dropout(x)
# Transformer Encoder
h = self.transformer_encoder(x, src_key_padding_mask=src_key_padding_mask)
# MLM予測:各位置で語彙全体での確率を出力
logits = self.mlm_head(h) # [batch, seq_len, vocab_size]
return logits
# (5) モデルを作成
config = ModelConfig(tokenizer)
model = DNN(config).to(device)
詳しくは第30回を参照してください。
説明メモ
- (1) データ作成にも使用したトークナイザーを読み込みます。
- (2) ModelConfigクラスでモデル構造やトークナイズの属性をまとめています。
- (3) MLMHeadクラスはマスク部分を予測する最終層のブロック、classification_headの重みに、トークン埋め込み層の重みをそのまま利用する重み共有をおこないます。
- (4) DNNクラスに最終的な流れを記載します。マスク化されたIDベクトルを入力、トークンと位置の埋め込み層、Transformer Encoder層、mlm_head層と経由して、最終出力となります。出力は各トークごとのlogitsとなります。出力される値の形状は(系列長,語彙数)= (64, 2000)の形になります。
2.3 誤差関数と誤差最小化の手法の選択
criterion = torch.nn.CrossEntropyLoss(ignore_index=-100)
optimizer = torch.optim.AdamW(model.parameters(), lr=0.0001)
説明メモ
-
ignore_index = -100で損失計算時に除外するIDを指定できます。「ー100」がデフォルト値となります。 - 学習率の部分は、config.learning_rate とすると格好良いのですが、今回はモデル部分以外の数値はそのまま記入する形になっています。学習率についても徐々に減少するタイプの方が効率的だと思います。今回そのままですが
2.4 変数更新のループ
学習ループの部分に第29回のマスク化処理(DataLoaderのcollate_fnを利用する方法)が入ります。学習ループを繰り返すたびに<mask>位置がランダムに変更されます。様々な<mask>位置を学習させるためにミニバッチサイズを小さめに設定しました。
# (1) DataLoaderで利用するcollate_fn
def mlm_sample(batch, mask_prob:float = 0.15):
"""
batch: [(x1,), (x2,),...,(xi,),...] というタプルのリストの予定
x1: tensor
"""
special_tokens_set = config.special_tokens_set # {0,1,2,3,4} # <pad>, <bos>, <eos>, <unk>, <mask>になる予定
normal_tokens_list = config.normal_tokens_list # range(5,10_000) # 語彙IDのspecial_tokensを除いたもの
# (★) 句読点も除外
no_mask_tokens = ["、", "。", ",", ".", ",", "."]
no_mask_ids = set(
token_id for token_id in [tokenizer.token_to_id(tok) for tok in no_mask_tokens]
if token_id is not None
)
special_tokens_set.update(no_mask_ids)
all_input_ids = []
all_labels = []
# (2) 各サンプルに対してループ処理
for item in batch:
original_ids = item[0] # item[0]でxiを取り出す
input_ids = original_ids.clone() # originalも使うのでcloneしておく
labels = torch.full_like(original_ids, -100) # ラベル:一旦 ID = -100 にしてから、該当部分のみ書き換える
for i in range(len(original_ids)):
# (3) 特殊トークンの場合は飛ばす torch.Tensorをintに戻してから判定
token_id = int(original_ids[i].item())
if token_id in special_tokens_set:
continue
# (4) 特殊トークン以外15%の確率が置き換え対象
if random.random() < mask_prob:
labels[i] = original_ids[i] # labelsに元のIDを入れる (教師データになる)
# 置き換え対象の処理 80/10/10 で マスク/別トークン/そのまま
rand = random.random()
if rand < 0.8:
input_ids[i] = config.mask_token_id # mask_id 今回はそのまま入力しているけど
elif rand < 0.9:
input_ids[i] = random.choice(normal_tokens_list) # 特殊トークンID以外のトークンで置き換え
else:
pass # 10%はそのまま
all_input_ids.append(input_ids)
all_labels.append(labels)
# リストをスタックして (bs, seq_Len) のテンソルに戻す
return torch.stack(all_input_ids), torch.stack(all_labels)
# (5) DataLoaderを利用してミニバッチ化
train_data = TensorDataset(x)
train_loader = DataLoader(
train_data,
batch_size=32,
shuffle=True,
collate_fn=mlm_sample,
drop_last=True,
num_workers=4
)
詳しくは第29回を参照してください。(★) 部分以外は第29回と同じになります。
説明メモ
- (1) torch.Tensorの x が入力され、<mask>化された学習データと対応する教師ラベルを出力する関数を定義します。
- (★) 今回のポイント: 学習データが少ないので、トークンに占める句読点の頻度が大きいみたい。試してみるとわかるのですが<mask>部分に、「、」や「。」を予測するというパターンにおちいる可能性が大きいのです。「強制的に句読点を予測するな!」ということで、句読点も学習対象トークンから除外しました4。
- (2) <mask>置き換え部分
- (3) 特殊トークンは飛ばします。
original_ids[i]はtorch.Tensorなので、item()を使って数値に戻します。これで、次のif文判定が誤判定になることはないかな。 - (4) 15%が置き換え対象、そのうち、「80/10/10」の割合で「マスク/別トークン/そのまま」となります。
- (5) TensorDatasetとDataLoaderを使い、ミニバッチ化。DataLiaderのオプションにcollate_fn=mlm_samlpeを指定します。
ここまでが準備編
長かった![]()
![]()
![]()
実際の学習ループはいつもと同じになります。その前に、精度計算の関数を定義しておきましょう。IDが「-100」になっている部分を除外する地味な面倒さ😱
def accuracy(y, t):
"""
マスク位置での予測精度を計算
"""
with torch.no_grad(): # no_grad()なくても問題ない
preds = torch.argmax(y, dim=-1)
mask = (t != -100) # 「-100」以外が<mask>部分
correct = (preds == t) & mask # <mask>部分で予測値と正解ラベルが等しいものが正解!
num_correct = correct.sum().item()
num_total = mask.sum().item()
accuracy = (num_correct / num_total) if num_total > 0 else 0.0
return accuracy
説明メモ
-
if num_total > 0としています。系列長が64と短めなので、学習対象(<mask>)が1箇所も登場しない可能性がありえます。予防的につけておきました。 - この関数内では勾配計算を行わないのでささやかながら torch.no_grad()の中で計算させてみたけど効果ないかも。
いよいよ学習ループになります。
DataLoaderのオプションに「drop_last=True」をつけているので、ミニバッチの最後の部分が適切に処理され、精度の計算部分が合計を割り算する形で正確に記述できます。第14回の「ミニバッチと変数更新」を参考にしてください。
基本的な流れは初回の頃から変わっていません。
LOOP = 1000 # マスク部分が正しく予測できることを確認したいので何度もループさせます。
model.train()
for epoch in range(LOOP):
total_loss = 0
total_acc = 0
num_batches = 0
for x, t in train_loader:
optimizer.zero_grad() # 勾配をリセットする
x = x.to(device) # (batch_size, seq_length)=(32, 64) # ここでGPUへ
t = t.to(device) # (batch_size, seq_length)=(32, 64)
y = model(x) # モデルの予測 # y.shape = (batch_size, seq_length, vocab_size)=(32, 64, 2000)
# (1) 損失計算時の形状に注意
loss = criterion(y.reshape(-1, config.vocab_size), t.reshape(-1)) # 損失計算
acc = accuracy(y, t) # 精度計算
loss.backward() # 各パラメータでの勾配計算
# (2) 勾配のノルムが大きくなりすぎないように制限
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step() # パラメータの更新
total_loss += loss.item()
total_acc += acc
num_batches += 1
if (epoch+1)%10 == 0:
print(f"{epoch+1}:\tloss:{total_loss/num_batches:.3f}\tacc:{total_acc/num_batches:.3f}")
マスク部分のトークンを正しく予測できているか確認したいので、精度が0.4〜0.5くらいまで学習を頑張ります。1時間〜2時間ほど待ちましょう。バッチサイズを小さくしてあるのでGPUメモリー(VRAM)は4GBもあれば十分動作しますが時間もかかります![]()
説明メモ
-
(1) 予測値 y の形状が (32, 64, 2000)、教師ラベル t の形状が(32, 64)となります。CrossEntropyLossを利用して64 トークン × 32 個 = 2048トークンについて、予測値 logits と教師ラベル t と比較すればよいので、y と t の形状を下記のように変形することになります。
loss = criterion(y.reshape(-1, config.vocab_size), t.reshape(-1))
-
(2) パラメータ$\theta_t$を更新する部分。基本的には、「$\theta_{t+1}=\theta_{t}-\eta \nabla L(\theta_t)$」という雰囲気の式でした。式からわかるように、パラメータを更新する時、微分した値が大きいとパラメータの更新が大きくなり挙動が不安定になることがあります。loss.backward()で求めた勾配について、「勾配が大きい場合は小さくなるように計算せよ!」という設定がclip_grad_normになります。
2.5 モデルの保存方法
ある程度時間もかかったし〜、折角なので事前学習したモデルを利用してファインチューニングも試してみたい😆ということで、モデルを保存しておきます。
torch.save({
"model_state_dict": model.state_dict(),
"config": config.__dict__, # configも一緒に保存
}, pretrain_filename)
復元するときは次のようになります。
model _file_name = "./model/unigram_2k.model" # 保存したモデル名
checkpoint = torch.load(model_file_name)
config = ModelConfig(tokenizer) # tokenizerも事前に読み込んでおきます
config.__dict__.update(checkpoint["config"]) # checkpoint["config"]辞書を使って、configをアップデート
model = DNN(config).to(device)
model.load_state_dict(checkpoint["model_state_dict"])
説明メモ
- checkpoint["config"]:保存したconfig
- checkpoint["config"]は辞書形式なので、updateを使い中身を保存したものへ置き換えます。
- checkpoipnt["model_state_dict"]: 保存したモデルの重み
地味です
よく見かける from_pretrained(保存したモデル)みたいなメソッドを利用する
config = ModelConfig.from_pretrained(model_id)
というスマート方法、複雑そうなので今回はパス。
3. 検証・<mask>予測
最後に穴埋め学習によって<mask>部分が正しく予測できているか確認してみましょう。このためだけに、頑張りました🌵
トークン1個だけを<mask>とするので、「徳川家康」のような単語を予測するわけではないので少し注意が必要です5。
import torch.nn.functional as F
# (1) テキストをidsへ
def encode_text_with_special_tokens(text, tokenizer, config):
"""
テキストをtokenizerに従い、idsへ変換する関数
text: 例 織田信<mask>は桶狭間の戦いで... : <mask>は1個に限定しておく
tokenizer: 学習時に使った tokenizer
戻り値: (1, seq_len) = (1, 64) の torch.LongTensor input_ids
"""
ids = tokenizer.encode(text).ids # 文字列をID化
ids = [config.bos_token_id] + ids + [config.eos_token_id] # <bos>, <eos> を追加
ids = ids[:config.seq_len] # 長すぎる場合は切る
if len(ids) < config.seq_len: # 足りない場合は<pad>
ids = ids + [config.pad_token_id] * (config.seq_len - len(ids))
# LongTensorに変換して出力
input_ids = torch.tensor([ids], dtype=torch.long, device=device)
return input_ids
# (2) top 5の表示
@torch.inference_mode()
def predict_mask_topk(model, tokenizer, config, text, topk=5):
"""
text中の <mask> 位置を見つけて,上位候補を返す
"""
model.eval()
input_ids = encode_text_with_special_tokens(text, tokenizer, config) # textをエンコード化<bos>や<eos>もついているぞ
logits = model(input_ids).to(device) # モデル出力: (1, seq_len, vocab_size) = (1, 64, 2000)
mask_positions = (input_ids[0] == config.mask_token_id).nonzero(as_tuple=True)[0] # <mask> の位置を探す
pos = mask_positions.item() # mask_positionはtorch.Tensorなので数値に戻す
mask_logits = logits[0, pos] # maski_position位置の語彙方向のスコア logitsを取得 (vocab_size)
probs = F.softmax(mask_logits, dim=-1) # 確率化 (自分が解釈するため)
top_probs, top_ids = torch.topk(probs, k=topk) # 上位 topk 個
# topkの(ID,トークン,確率)を戻り値に指定
candidates = []
for prob, token_id in zip(top_probs.tolist(), top_ids.tolist()):
token_str = tokenizer.id_to_token(token_id)
candidates.append((token_id, token_str, prob))
return {"position": pos,"candidates": candidates}
<mask>が一箇所だけある文のみ扱います。2個以上はNGだよ😱(エラーになります)
基本的に、
- 予測させたい<mask>付きの文をIDに変換、
- <mask>位置のmodel出力について、大きい順に5つ(topk=5)
を求めればOKです。
説明メモ
- (1) ベタな関数ですが順番にID化、<bos>などの追加、長さの調整を行います。
- (2) (1)の関数を利用して、文章中の<mask>部分の予想語彙を出力させます。こちらもベタな関数です。順番に、文をID化、学習したモデルで予測、出力値 (logits) の<mask>部分の確率をsoftmaxで計算して、上位5つまでのトークンと確率を出力させます。
# 例文
text = "安土桃<mask>時代について" # 文が途中で切れていてもOK
#text = "江戸時代は、徳<mask>家康によって"
#text = "安徳天皇を擁護していてもその即位はクーデターによるものであり、<mask>が自己の立場を正当化することは困難だった。"
results = predict_mask_topk(model, tokenizer, config, text, topk=5)
# pandasを利用して表の形で表示させてみた
import pandas as pd
print(f"mask position = {results['position']}")
df = pd.DataFrame(results["candidates"] , columns=["id", "token", "prob"])
print(df)
例1〜例3の文章で試してみました。どれも正しく予測できています。3番目は2文字からなるトークンです。学習データの文章から難しそうなのを探して、語彙を変更してみました6。トークンは3位までを掲載しました。
例1. 正解「山」![]()
text = "安土桃<mask>時代について"
| id | token | prob | |
|---|---|---|---|
| 0 | 72 | 山 | 0.843311 |
| 1 | 5 | の | 0.045271 |
| 2 | 113 | 土 | 0.013663 |
例2. 正解「川」![]()
text = "江戸時代は、徳<mask>家康によって"
| id | token | prob | |
|---|---|---|---|
| 0 | 954 | 川 | 0.992377 |
| 1 | 199 | 田 | 0.001144 |
| 2 | 596 | 良 | 0.000729 |
例3. 正解「平氏」
text = "安徳天皇を擁護していてもその即位はクーデターによるものであり、<mask>が自己の立場を正当化することは困難だった。"
| id | token | prob | |
|---|---|---|---|
| 0 | 35 | 平氏 | 0.305728 |
| 1 | 140 | 清盛 | 0.146891 |
| 2 | 84 | 武士 | 0.107312 |
考察
分野が異なると、全くだめになります。学習している文章に存在していないので当然なのでしょう。少し経済っぽい内容も試してみたのですが、「インフレ」ネタは昭和の部分に記載があるかな?正解してしまいました。第2位の「紙」で紙幣もなかなかなもの😆
「所定欄」や「所定の欄」の「欄」これは、NGでした。メールアドレス関連、これは学習した文にないのだろう。「すること」と予想しています。
ユーザーやタグという用語もNGみたい。カタカナならとりあえず「ー」つけとけ!という感じでした。
当然ですが学習する内容に依存する形で<mask>部分を理解しているようです。様々な分野のテキストを学習させればうまくできそうな予感もします。しかし、「大規模コーパス化すればうまく学習できるのか?」みたいな期待と不安がありそうですよね🍀少し歴史も調べてみたいものです🌸
参考の論文
- Devlin el. al.(2018) "BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding"
- Liu et al. (2019) "RoBERTa: A Robustly Optimized BERT Pretraining Approach"
掲載するまでもないほど有名なBERTとRoBERTaです![]()
著者名を全員掲載せずで![]()
![]()
次回
せっかく事前学習してみたので、学習したモデルを利用して「転移学習」しました![]()
- マスク化とミニバッチ(1回目)
- BERTタイプのMLM学習用モデルと重み共有(2回目)
- 実際に事前学習(3回目・今回)
- 自作事前学習モデルでファインチューニング(4回目)
- Datasetクラスの改良(5回目)
目次ページ
注
-
WordPieceだとトークンサイズが2000個にならなかった気がします🤔 ↩
-
RoBERTaでの事前学習データの作成を参考にアレンジしてみました。RoBERTaではByteLevel+BPEのトークナイザーを使っているのですが、学習データ作成方法を少し寄せてみました。この方法だと少ないデータの等長の系列長でも無駄なく情報を扱うことができるはず。 ↩
-
今回はうまく<mask>予測できたし転移学習も成功しているのでこのまま使いました。まあ、できるだけ簡潔、でもうまくできるを目指していますしね
↩ -
コーパスのデータ量が十分にあれば、句読点の割合が相対的に小さくなるのかな?変わらないような気もするけど、CC100とかどうなんだろうか?時間あるときに調べてみるのも面白いかもしれない。 ↩
-
利用するトークナイザーだと「徳川家康」は「徳/川/家/康」 と4トークンになります。 ↩
-
「山」・「川」ときたら3番目は「海」
いい例文が見つからなかったので、wikipediaっぽい硬めの文になっています。 ↩




