.. _sec_natural-language-inference-bert:
Doğal Dil Çıkarımı: BERT İnce Ayarı
===================================
Bu ünitenin önceki kısımlarında, SNLI veri kümesinde
(:numref:`sec_natural-language-inference-and-dataset` içindde
açıklandığı gibi) doğal dil çıkarım görevi için dikkat tabanlı bir
mimari (:numref:`sec_natural-language-inference-attention` içinde)
tasarladık. Şimdi BERT ince ayarı yaparak bu görevi tekrar gözden
geçiriyoruz. :numref:`sec_finetuning-bert` içinde tartışıldığı gibi,
doğal dil çıkarımı bir dizi düzeyinde metin çifti sınıflandırma
sorunudur ve BERT ince ayarı yalnızca :numref:`fig_nlp-map-nli-bert`
içinde gösterildiği gibi ek bir MLP tabanlı mimari gerektirir.
.. _fig_nlp-map-nli-bert:
.. figure:: ../img/nlp-map-nli-bert.svg
Bu bölüm, doğal dil çıkarımı için önceden eğitilmiş BERT'i MLP
tabanlı bir mimariye besler.
Bu bölümde, BERT'in önceden eğitilmiş küçük bir sürümünü indireceğiz,
ardından SNLI veri kümesinde doğal dil çıkarımı için ince ayarını
yapacağız.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
import json
import multiprocessing
import os
from d2l import mxnet as d2l
from mxnet import gluon, np, npx
from mxnet.gluon import nn
npx.set_np()
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
import json
import multiprocessing
import os
import torch
from torch import nn
from d2l import torch as d2l
.. raw:: html
.. raw:: html
Önceden Eğitilmiş BERT'i Yükleme
--------------------------------
BERT'i :numref:`sec_bert-dataset` ve :numref:`sec_bert-pretraining`
içinde WikiText-2 veri kümelerinde nasıl ön eğitebileceğimizi açıkladık
(orijinal BERT modelinin çok daha büyük külliyatlar üzerinde önceden
eğitildiğini unutmayın). :numref:`sec_bert-pretraining` içinde
tartışıldığı gibi, orijinal BERT modelinin yüz milyonlarca parametresi
vardır. Aşağıda, önceden eğitilmiş BERT'in iki versiyonunu sunuyoruz:
“bert.base” ince ayar yapmak için çok sayıda hesaplama kaynağı
gerektiren orijinal BERT temel modeli kadar büyüktür, “bert.small” ise
gösterimi kolaylaştırmak için küçük bir versiyondur.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
d2l.DATA_HUB['bert.base'] = (d2l.DATA_URL + 'bert.base.zip',
'7b3820b35da691042e5d34c0971ac3edbd80d3f4')
d2l.DATA_HUB['bert.small'] = (d2l.DATA_URL + 'bert.small.zip',
'a4e718a47137ccd1809c9107ab4f5edd317bae2c')
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
d2l.DATA_HUB['bert.base'] = (d2l.DATA_URL + 'bert.base.torch.zip',
'225d66f04cae318b841a13d32af3acc165f253ac')
d2l.DATA_HUB['bert.small'] = (d2l.DATA_URL + 'bert.small.torch.zip',
'c72329e68a732bef0452e4b96a1c341c8910f81f')
.. raw:: html
.. raw:: html
Önceden eğitilmiş BERT modeli, sözcük dağarcığını tanımlayan bir
“vocab.json” dosyasını ve önceden eğitilmiş parametrelerin olduğu
“pretrained.params” dosyasını içerir. Önceden eğitilmiş BERT
parametrelerini yüklemek için aşağıdaki ``load_pretrained_model``
işlevini uyguluyoruz.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
def load_pretrained_model(pretrained_model, num_hiddens, ffn_num_hiddens,
num_heads, num_layers, dropout, max_len, devices):
data_dir = d2l.download_extract(pretrained_model)
# Önceden tanımlanmış kelimeleri yüklemek için boş bir kelime
# dağarcığı tanımla
vocab = d2l.Vocab()
vocab.idx_to_token = json.load(open(os.path.join(data_dir, 'vocab.json')))
vocab.token_to_idx = {token: idx for idx, token in enumerate(
vocab.idx_to_token)}
bert = d2l.BERTModel(len(vocab), num_hiddens, ffn_num_hiddens, num_heads,
num_layers, dropout, max_len)
# Önceden eğitilmiş BERT parametrelerini yükle
bert.load_parameters(os.path.join(data_dir, 'pretrained.params'),
ctx=devices)
return bert, vocab
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
def load_pretrained_model(pretrained_model, num_hiddens, ffn_num_hiddens,
num_heads, num_layers, dropout, max_len, devices):
data_dir = d2l.download_extract(pretrained_model)
# Önceden tanımlanmış kelimeleri yüklemek için boş bir kelime
# dağarcığı tanımla
vocab = d2l.Vocab()
vocab.idx_to_token = json.load(open(os.path.join(data_dir, 'vocab.json')))
vocab.token_to_idx = {token: idx for idx, token in enumerate(
vocab.idx_to_token)}
bert = d2l.BERTModel(len(vocab), num_hiddens, norm_shape=[256],
ffn_num_input=256, ffn_num_hiddens=ffn_num_hiddens,
num_heads=4, num_layers=2, dropout=0.2,
max_len=max_len, key_size=256, query_size=256,
value_size=256, hid_in_features=256,
mlm_in_features=256, nsp_in_features=256)
# Önceden eğitilmiş BERT parametrelerini yükle
bert.load_state_dict(torch.load(os.path.join(data_dir,
'pretrained.params')))
return bert, vocab
.. raw:: html
.. raw:: html
Makinelerin çoğunda gösterimi kolaylaştırmak için, bu bölümde önceden
eğitilmiş BERT'in küçük versiyonunu (“bert.small”) yükleyip ona ince
ayar yapacağız. Alıştırmada, test doğruluğunu önemli ölçüde artırmak
için çok daha büyük “bert.base”'in nasıl ince ayar yapılacağını
göstereceğiz.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
devices = d2l.try_all_gpus()
bert, vocab = load_pretrained_model(
'bert.small', num_hiddens=256, ffn_num_hiddens=512, num_heads=4,
num_layers=2, dropout=0.1, max_len=512, devices=devices)
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
devices = d2l.try_all_gpus()
bert, vocab = load_pretrained_model(
'bert.small', num_hiddens=256, ffn_num_hiddens=512, num_heads=4,
num_layers=2, dropout=0.1, max_len=512, devices=devices)
.. raw:: html
.. raw:: html
BERT İnce Ayarı İçin Veri Kümesi
--------------------------------
SNLI veri kümesinde aşağı akış görevi doğal dil çıkarımı için,
özelleştirilmiş bir veri kümesi sınıfı ``SNLIBERTDataset`` tanımlıyoruz.
Her örnekte, öncül ve hipotez bir çift metin dizisi oluşturur ve
:numref:`fig_bert-two-seqs` üzerinde tasvir edildiği gibi bir BERT
girdi dizisine paketlenir. Hatırlayalım;
:numref:`subsec_bert_input_rep` bölüm kimlikleri bir BERT girdi
dizisinde öncül ve hipotezi ayırt etmek için kullanılır. Bir BERT girdi
dizisinin (``max_len``) önceden tanımlanmış maksimum uzunluğu ile, girdi
metin çiftinin daha uzun olanının son belirteci, ``max_len`` karşılanana
kadar ortadan kaldırılmaya devam eder. BERT ince ayarında SNLI veri
kümesinin oluşturulmasını hızlandırmak için, paralel olarak eğitim veya
test örnekleri oluşturmada 4 işçi işlem kullanıyoruz.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
class SNLIBERTDataset(gluon.data.Dataset):
def __init__(self, dataset, max_len, vocab=None):
all_premise_hypothesis_tokens = [[
p_tokens, h_tokens] for p_tokens, h_tokens in zip(
*[d2l.tokenize([s.lower() for s in sentences])
for sentences in dataset[:2]])]
self.labels = np.array(dataset[2])
self.vocab = vocab
self.max_len = max_len
(self.all_token_ids, self.all_segments,
self.valid_lens) = self._preprocess(all_premise_hypothesis_tokens)
print('read ' + str(len(self.all_token_ids)) + ' examples')
def _preprocess(self, all_premise_hypothesis_tokens):
pool = multiprocessing.Pool(4) # Use 4 worker processes
out = pool.map(self._mp_worker, all_premise_hypothesis_tokens)
all_token_ids = [
token_ids for token_ids, segments, valid_len in out]
all_segments = [segments for token_ids, segments, valid_len in out]
valid_lens = [valid_len for token_ids, segments, valid_len in out]
return (np.array(all_token_ids, dtype='int32'),
np.array(all_segments, dtype='int32'),
np.array(valid_lens))
def _mp_worker(self, premise_hypothesis_tokens):
p_tokens, h_tokens = premise_hypothesis_tokens
self._truncate_pair_of_tokens(p_tokens, h_tokens)
tokens, segments = d2l.get_tokens_and_segments(p_tokens, h_tokens)
token_ids = self.vocab[tokens] + [self.vocab['
']] \
* (self.max_len - len(tokens))
segments = segments + [0] * (self.max_len - len(segments))
valid_len = len(tokens)
return token_ids, segments, valid_len
def _truncate_pair_of_tokens(self, p_tokens, h_tokens):
# BERT girdisi için '', '' ve '' belirteçleri
# için yer ayırın
while len(p_tokens) + len(h_tokens) > self.max_len - 3:
if len(p_tokens) > len(h_tokens):
p_tokens.pop()
else:
h_tokens.pop()
def __getitem__(self, idx):
return (self.all_token_ids[idx], self.all_segments[idx],
self.valid_lens[idx]), self.labels[idx]
def __len__(self):
return len(self.all_token_ids)
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
class SNLIBERTDataset(torch.utils.data.Dataset):
def __init__(self, dataset, max_len, vocab=None):
all_premise_hypothesis_tokens = [[
p_tokens, h_tokens] for p_tokens, h_tokens in zip(
*[d2l.tokenize([s.lower() for s in sentences])
for sentences in dataset[:2]])]
self.labels = torch.tensor(dataset[2])
self.vocab = vocab
self.max_len = max_len
(self.all_token_ids, self.all_segments,
self.valid_lens) = self._preprocess(all_premise_hypothesis_tokens)
print('read ' + str(len(self.all_token_ids)) + ' examples')
def _preprocess(self, all_premise_hypothesis_tokens):
pool = multiprocessing.Pool(4) # Use 4 worker processes
out = pool.map(self._mp_worker, all_premise_hypothesis_tokens)
all_token_ids = [
token_ids for token_ids, segments, valid_len in out]
all_segments = [segments for token_ids, segments, valid_len in out]
valid_lens = [valid_len for token_ids, segments, valid_len in out]
return (torch.tensor(all_token_ids, dtype=torch.long),
torch.tensor(all_segments, dtype=torch.long),
torch.tensor(valid_lens))
def _mp_worker(self, premise_hypothesis_tokens):
p_tokens, h_tokens = premise_hypothesis_tokens
self._truncate_pair_of_tokens(p_tokens, h_tokens)
tokens, segments = d2l.get_tokens_and_segments(p_tokens, h_tokens)
token_ids = self.vocab[tokens] + [self.vocab['
']] \
* (self.max_len - len(tokens))
segments = segments + [0] * (self.max_len - len(segments))
valid_len = len(tokens)
return token_ids, segments, valid_len
def _truncate_pair_of_tokens(self, p_tokens, h_tokens):
# BERT girdisi için '', '' ve '' belirteçleri
# için yer ayırın
while len(p_tokens) + len(h_tokens) > self.max_len - 3:
if len(p_tokens) > len(h_tokens):
p_tokens.pop()
else:
h_tokens.pop()
def __getitem__(self, idx):
return (self.all_token_ids[idx], self.all_segments[idx],
self.valid_lens[idx]), self.labels[idx]
def __len__(self):
return len(self.all_token_ids)
.. raw:: html
.. raw:: html
SNLI veri kümesini indirdikten sonra, ``SNLIBERTDataset`` sınıfından
örnek yaratarak eğitim ve test örnekleri oluşturuyoruz. Bu tür örnekler,
doğal dil çıkarımlarının eğitimi ve test edilmesi sırasında
minigruplarda okunacaktır.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
# Yetersiz bellek hatası varsa `batch_size` değerini azaltın. Orijinal BERT
# modelinde, `max_len` = 512'dir.
batch_size, max_len, num_workers = 512, 128, d2l.get_dataloader_workers()
data_dir = d2l.download_extract('SNLI')
train_set = SNLIBERTDataset(d2l.read_snli(data_dir, True), max_len, vocab)
test_set = SNLIBERTDataset(d2l.read_snli(data_dir, False), max_len, vocab)
train_iter = gluon.data.DataLoader(train_set, batch_size, shuffle=True,
num_workers=num_workers)
test_iter = gluon.data.DataLoader(test_set, batch_size,
num_workers=num_workers)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
read 549367 examples
read 9824 examples
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
# Yetersiz bellek hatası varsa `batch_size` değerini azaltın. Orijinal BERT
# modelinde, `max_len` = 512'dir.
batch_size, max_len, num_workers = 512, 128, d2l.get_dataloader_workers()
data_dir = d2l.download_extract('SNLI')
train_set = SNLIBERTDataset(d2l.read_snli(data_dir, True), max_len, vocab)
test_set = SNLIBERTDataset(d2l.read_snli(data_dir, False), max_len, vocab)
train_iter = torch.utils.data.DataLoader(train_set, batch_size, shuffle=True,
num_workers=num_workers)
test_iter = torch.utils.data.DataLoader(test_set, batch_size,
num_workers=num_workers)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
read 549367 examples
read 9824 examples
.. raw:: html
.. raw:: html
BERT İnce Ayarı
---------------
:numref:`fig_bert-two-seqs` içinde belirtildiği gibi, doğal dil
çıkarımı için BERT ince ayarı yalnızca iki tam bağlı katmandan oluşan ek
bir MLP gerektirir (aşağıdaki ``BERTClassifier`` sınıfında
``self.hidden`` ve ``self.output``). Bu MLP, hem öncül hem de hipotezin
bilgilerini kodlayan özel “” belirtecinin BERT temsilini, doğal dil
çıkarımının üç çıktısına dönüştürür: Gerekçe, çelişki ve tarafsızlık.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
class BERTClassifier(nn.Block):
def __init__(self, bert):
super(BERTClassifier, self).__init__()
self.encoder = bert.encoder
self.hidden = bert.hidden
self.output = nn.Dense(3)
def forward(self, inputs):
tokens_X, segments_X, valid_lens_x = inputs
encoded_X = self.encoder(tokens_X, segments_X, valid_lens_x)
return self.output(self.hidden(encoded_X[:, 0, :]))
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
class BERTClassifier(nn.Module):
def __init__(self, bert):
super(BERTClassifier, self).__init__()
self.encoder = bert.encoder
self.hidden = bert.hidden
self.output = nn.Linear(256, 3)
def forward(self, inputs):
tokens_X, segments_X, valid_lens_x = inputs
encoded_X = self.encoder(tokens_X, segments_X, valid_lens_x)
return self.output(self.hidden(encoded_X[:, 0, :]))
.. raw:: html
.. raw:: html
Aşağıda, önceden eğitilmiş BERT modeli ``bert``, aşağı akış uygulaması
için ``BERTClassifier`` örneği ``net``'e beslenir. BERT ince ayarının
ortak uygulamalarında, sadece ek MLP'nin (``net.output``) çıktı
tabakasının parametreleri sıfırdan öğrenilecektir. Önceden eğitilmiş
BERT kodlayıcısının (``net.encoder``) ve ek MLP'nin gizli katmanının
(``net.hidden``) tüm parametreleri ince ayarlanacaktır.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
net = BERTClassifier(bert)
net.output.initialize(ctx=devices)
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
net = BERTClassifier(bert)
.. raw:: html
.. raw:: html
:numref:`sec_bert` içinde hem ``MaskLM`` sınıfının hem de
``NextSentencePred`` sınıfının konuşlanmış MLP'lerinde parametrelere
sahip olduğunu hatırlayın. Bu parametreler önceden eğitilmiş ``bert``
BERT modelinin ve dolayısıyla ``net`` içindeki parametrelerin bir
parçasıdır. Bununla birlikte, bu tür parametreler sadece maskeli dil
modelleme kaybını ve ön eğitim sırasında bir sonraki cümle tahmini
kaybını hesaplamak içindir. Bu iki kayıp işlevi, aşağı akış
uygulamalarının ince ayarını yapmakla ilgisizdir, bu nedenle, BERT ince
ayarı yapıldığında ``MaskLM`` ve ``NextSentencePred``'de kullanılan
MLP'lerin parametreleri güncellenmez (bozulmaz).
Dondurulmuş gradyanlara sahip parametrelere izin vermek için
``ignore_stale_grad=True`` işareti ``d2l.train_batch_ch13``'in ``step``
işlevinde ayarlanır. Bu işlevi, SNLI'nin eğitim kümesini
(``train_iter``) ve test kümesini (``test_iter``) kullanarak ``net``
modelini eğitmek ve değerlendirmek için kullanıyoruz. Sınırlı hesaplama
kaynakları nedeniyle, eğitim ve test doğruluğu daha da
iyileştirilebilir: Tartışmalarını alıştırmalara bırakıyoruz.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
lr, num_epochs = 1e-4, 5
trainer = gluon.Trainer(net.collect_params(), 'adam', {'learning_rate': lr})
loss = gluon.loss.SoftmaxCrossEntropyLoss()
d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, devices,
d2l.split_batch_multi_inputs)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
loss 0.478, train acc 0.811, test acc 0.788
6949.0 examples/sec on [gpu(0), gpu(1)]
.. figure:: output_natural-language-inference-bert_1857e6_75_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
lr, num_epochs = 1e-4, 5
trainer = torch.optim.Adam(net.parameters(), lr=lr)
loss = nn.CrossEntropyLoss(reduction='none')
d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, devices)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
loss 0.519, train acc 0.791, test acc 0.782
10280.0 examples/sec on [device(type='cuda', index=0), device(type='cuda', index=1)]
.. figure:: output_natural-language-inference-bert_1857e6_78_1.svg
.. raw:: html
.. raw:: html
Özet
----
- SNLI veri kümesindeki doğal dil çıkarımı gibi aşağı akış uygulamaları
için önceden eğitilmiş BERT modeline ince ayar yapabiliriz.
- İnce ayar sırasında BERT modeli aşağı akış uygulaması için modelin
bir parçası haline gelir. Sadece ön eğitim kaybı ile ilgili
parametreler ince ayar sırasında güncellenmeyecektir.
Alıştırmalar
------------
1. Hesaplama kaynağınız izin veriyorsa, orijinal BERT temel modeli kadar
büyük olan, çok daha büyük, önceden eğitilmiş BERT modeline ince ayar
yapın. ``load_pretrained_model`` işlevinde bağımsız değişkenleri şu
şekilde ayarlayın: 'bert.small' yerine 'bert.base' ile kullanın,
sırasıyla ``num_hiddens=256``, ``ffn_num_hiddens=512``,
``num_heads=4`` ve ``num_layers=2`` ile 768, 3072, 12 ve 12
değerlerini değiştirin. İnce ayar dönemlerini artırarak (ve
muhtemelen diğer hiper parametreleri ayarlayarak), 0.86'dan daha
yüksek bir test doğruluğu elde edebilir misiniz?
2. Bir çift dizi uzunluk oranlarına göre nasıl budanır? Bu çift budama
yöntemini ve ``SNLIBERTDataset`` sınıfında kullanılan yöntemi
karşılaştırın. Artıları ve eksileri nelerdir?
.. raw:: html
.. raw:: html
`Tartışmalar `__
.. raw:: html
.. raw:: html
`Tartışmalar `__
.. raw:: html
.. raw:: html