9.5. Makine Çevirisi ve Veri Kümesi
Open the notebook in Colab
Open the notebook in Colab
Open the notebook in Colab
Open the notebook in SageMaker Studio Lab

Doğal dil işlemenin anahtarı olan dil modellerini tasarlamak için RNN kullandık. Diğer bir amiral gemisi kıyaslaması, girdi dizilerini çıktı dizilerine dönüştüren dizi dönüştürme modelleri için merkezi bir problem düzlemi olan makine çevirisidir. Çeşitli modern yapay zeka uygulamalarında önemli bir rol oynayan dizi dönüştürme modelleri, bu bölümün geri kalanının ve sonraki bölümün, Section 10, odağını oluşturacaktır. Bu amaçla, bu bölüm makine çevirisi sorununu ve daha sonra kullanılacak veri kümesini anlatır.

Makine çevirisi bir dizinin bir dilden diğerine otomatik çevirisidir. Aslında, bu alan, özellikle II. Dünya Savaşı’nda dil kodlarını kırmak için bilgisayarların kullanılması göz önüne alınarak, sayısal bilgisayarların icat edilmesinin kısa bir süre sonrasından 1940’lara kadar uzanabilir. Onlarca yıldır, bu alanda, istatistiksel yaklaşımlar, (Brown et al., 1990, Brown et al., 1988), sinir ağlarını kullanarak uçtan uca öğrenmenin yükselmesinin öncesine kadar baskın olmuştur. İkincisi, çeviri modeli ve dil modeli gibi bileşenlerde istatistiksel analiz içeren istatistiksel makine çevirisinden ayırt edilmesi için genellikle sinirsel makine çevirisi olarak adlandırılır.

Uçtan uca öğrenmeyi vurgulayan bu kitap, sinirsel makine çevirisi yöntemlerine odaklanacaktır. Külliyatı tek bir dil olan Section 8.3 içindeki dil modeli problemimizden farklı olarak, makine çevirisi veri kümeleri sırasıyla kaynak dilde ve hedef dilde bulunan metin dizileri çiftlerinden oluşmaktadır. Bu nedenle, dil modelleme için ön işleme rutinini yeniden kullanmak yerine, makine çevirisi veri kümelerini ön işlemek için farklı bir yol gerekir. Aşağıda, önceden işlenmiş verilerin eğitim için minigruplara nasıl yükleneceğini gösteriyoruz.

import os
from d2l import mxnet as d2l
from mxnet import np, npx

npx.set_np()
import os
import torch
from d2l import torch as d2l
import os
import tensorflow as tf
from d2l import tensorflow as d2l

9.5.1. Veri Kümesini İndirme ve Ön işleme

Başlangıç olarak, Tatoeba Projesi’nden iki dilli cümle çiftleri’nden oluşan bir İngiliz-Fransız veri kümesini indiriyoruz. Veri kümedeki her satır, bir sekmeyle ayrılmış İngilizce metin dizisi ve çevrilmiş Fransızca metin dizisi çiftidir. Her metin dizisinin sadece bir cümle veya birden çok cümleden oluşan bir paragraf olabileceğini unutmayın. İngilizce’nin Fransızca’ya çevrildiği bu makine çevirisi probleminde, İngilizce kaynak dil, Fransızca ise hedef dildir.

#@save
d2l.DATA_HUB['fra-eng'] = (d2l.DATA_URL + 'fra-eng.zip',
                           '94646ad1522d915e7b0f9296181140edcf86a4f5')

#@save
def read_data_nmt():
    """İnglizce-Fransızca veri kümesini yükle."""
    data_dir = d2l.download_extract('fra-eng')
    with open(os.path.join(data_dir, 'fra.txt'), 'r') as f:
        return f.read()

raw_text = read_data_nmt()
print(raw_text[:75])
Go. Va !
Hi. Salut !
Run!        Cours !
Run!        Courez !
Who?        Qui ?
Wow!        Ça alors !
#@save
d2l.DATA_HUB['fra-eng'] = (d2l.DATA_URL + 'fra-eng.zip',
                           '94646ad1522d915e7b0f9296181140edcf86a4f5')

#@save
def read_data_nmt():
    """İnglizce-Fransızca veri kümesini yükle."""
    data_dir = d2l.download_extract('fra-eng')
    with open(os.path.join(data_dir, 'fra.txt'), 'r') as f:
        return f.read()

raw_text = read_data_nmt()
print(raw_text[:75])
Go. Va !
Hi. Salut !
Run!        Cours !
Run!        Courez !
Who?        Qui ?
Wow!        Ça alors !
#@save
d2l.DATA_HUB['fra-eng'] = (d2l.DATA_URL + 'fra-eng.zip',
                           '94646ad1522d915e7b0f9296181140edcf86a4f5')

#@save
def read_data_nmt():
    """İnglizce-Fransızca veri kümesini yükle."""
    data_dir = d2l.download_extract('fra-eng')
    with open(os.path.join(data_dir, 'fra.txt'), 'r') as f:
        return f.read()

raw_text = read_data_nmt()
print(raw_text[:75])
Go. Va !
Hi. Salut !
Run!        Cours !
Run!        Courez !
Who?        Qui ?
Wow!        Ça alors !

Veri kümesini indirdikten sonra, ham metin verileri için birkaç ön işleme adımı ile devam ediyoruz. Örneğin, aralıksız boşluğu boşlukla değiştirir, büyük harfleri küçük harflere dönüştürür ve sözcüklerle noktalama işaretleri arasına boşluk ekleriz.

#@save
def preprocess_nmt(text):
    """İngilizce-Fransızca veri kümesini ön işle."""
    def no_space(char, prev_char):
        return char in set(',.!?') and prev_char != ' '

    # Bölünmez boşluğu boşlukla değiştirin ve büyük harfleri küçük
    # harflere dönüştürün
    text = text.replace('\u202f', ' ').replace('\xa0', ' ').lower()
    # Sözcükler ve noktalama işaretleri arasına boşluk ekleyin
    out = [' ' + char if i > 0 and no_space(char, text[i - 1]) else char
           for i, char in enumerate(text)]
    return ''.join(out)

text = preprocess_nmt(raw_text)
print(text[:80])
go .        va !
hi .        salut !
run !       cours !
run !       courez !
who ?       qui ?
wow !       ça alors !
#@save
def preprocess_nmt(text):
    """İngilizce-Fransızca veri kümesini ön işle."""
    def no_space(char, prev_char):
        return char in set(',.!?') and prev_char != ' '

    # Bölünmez boşluğu boşlukla değiştirin ve büyük harfleri küçük
    # harflere dönüştürün
    text = text.replace('\u202f', ' ').replace('\xa0', ' ').lower()
    # Sözcükler ve noktalama işaretleri arasına boşluk ekleyin
    out = [' ' + char if i > 0 and no_space(char, text[i - 1]) else char
           for i, char in enumerate(text)]
    return ''.join(out)

text = preprocess_nmt(raw_text)
print(text[:80])
go .        va !
hi .        salut !
run !       cours !
run !       courez !
who ?       qui ?
wow !       ça alors !
#@save
def preprocess_nmt(text):
    """İngilizce-Fransızca veri kümesini ön işle."""
    def no_space(char, prev_char):
        return char in set(',.!?') and prev_char != ' '

    # Bölünmez boşluğu boşlukla değiştirin ve büyük harfleri küçük
    # harflere dönüştürün
    text = text.replace('\u202f', ' ').replace('\xa0', ' ').lower()
    # Sözcükler ve noktalama işaretleri arasına boşluk ekleyin
    out = [' ' + char if i > 0 and no_space(char, text[i - 1]) else char
           for i, char in enumerate(text)]
    return ''.join(out)

text = preprocess_nmt(raw_text)
print(text[:80])
go .        va !
hi .        salut !
run !       cours !
run !       courez !
who ?       qui ?
wow !       ça alors !

9.5.2. Andıçlama

Section 8.3 içindeki karakter düzeyinde andıçlara ayırmaktan farklı olarak, makine çevirisi için burada kelime düzeyinde andıçlamayı tercih ediyoruz (son teknoloji modeller daha gelişmiş andıçlama teknikleri kullanabilir). Aşağıdaki tokenize_nmt işlevi, her andıç bir sözcük veya noktalama işareti olduğu ilk num_examples tane metin dizisi çiftini andıçlar. Bu işlev, andıç listelerinden oluşan iki liste döndürür: source (kaynak) ve target (hedef). Özellikle, source[i] kaynak dilde (İngilizce burada) \(i\). metin dizisinden andıçların bir listesidir ve target[i] hedef dildekileri (Fransızca burada) içerir.

#@save
def tokenize_nmt(text, num_examples=None):
    """İngilizce-Fransızca veri kümesini andıçla."""
    source, target = [], []
    for i, line in enumerate(text.split('\n')):
        if num_examples and i > num_examples:
            break
        parts = line.split('\t')
        if len(parts) == 2:
            source.append(parts[0].split(' '))
            target.append(parts[1].split(' '))
    return source, target

source, target = tokenize_nmt(text)
source[:6], target[:6]
([['go', '.'],
  ['hi', '.'],
  ['run', '!'],
  ['run', '!'],
  ['who', '?'],
  ['wow', '!']],
 [['va', '!'],
  ['salut', '!'],
  ['cours', '!'],
  ['courez', '!'],
  ['qui', '?'],
  ['ça', 'alors', '!']])
#@save
def tokenize_nmt(text, num_examples=None):
    """İngilizce-Fransızca veri kümesini andıçla."""
    source, target = [], []
    for i, line in enumerate(text.split('\n')):
        if num_examples and i > num_examples:
            break
        parts = line.split('\t')
        if len(parts) == 2:
            source.append(parts[0].split(' '))
            target.append(parts[1].split(' '))
    return source, target

source, target = tokenize_nmt(text)
source[:6], target[:6]
([['go', '.'],
  ['hi', '.'],
  ['run', '!'],
  ['run', '!'],
  ['who', '?'],
  ['wow', '!']],
 [['va', '!'],
  ['salut', '!'],
  ['cours', '!'],
  ['courez', '!'],
  ['qui', '?'],
  ['ça', 'alors', '!']])
#@save
def tokenize_nmt(text, num_examples=None):
    """İngilizce-Fransızca veri kümesini andıçla."""
    source, target = [], []
    for i, line in enumerate(text.split('\n')):
        if num_examples and i > num_examples:
            break
        parts = line.split('\t')
        if len(parts) == 2:
            source.append(parts[0].split(' '))
            target.append(parts[1].split(' '))
    return source, target

source, target = tokenize_nmt(text)
source[:6], target[:6]
([['go', '.'],
  ['hi', '.'],
  ['run', '!'],
  ['run', '!'],
  ['who', '?'],
  ['wow', '!']],
 [['va', '!'],
  ['salut', '!'],
  ['cours', '!'],
  ['courez', '!'],
  ['qui', '?'],
  ['ça', 'alors', '!']])

Metin dizisi başına andıç sayısının histogramını çizelim. Bu basit İngilizce-Fransız veri kümesinde, metin dizilerinin çoğunun 20’den az andıcı vardır.

#@save
def show_list_len_pair_hist(legend, xlabel, ylabel, xlist, ylist):
    """Liste uzunluğu çiftleri için histogramı çizin."""
    d2l.set_figsize()
    _, _, patches = d2l.plt.hist(
        [[len(l) for l in xlist], [len(l) for l in ylist]])
    d2l.plt.xlabel(xlabel)
    d2l.plt.ylabel(ylabel)
    for patch in patches[1].patches:
        patch.set_hatch('/')
    d2l.plt.legend(legend)

show_list_len_pair_hist(['source', 'target'], '# tokens per sequence',
                        'count', source, target);
../_images/output_machine-translation-and-dataset_887557_51_0.svg
#@save
def show_list_len_pair_hist(legend, xlabel, ylabel, xlist, ylist):
    """Liste uzunluğu çiftleri için histogramı çizin."""
    d2l.set_figsize()
    _, _, patches = d2l.plt.hist(
        [[len(l) for l in xlist], [len(l) for l in ylist]])
    d2l.plt.xlabel(xlabel)
    d2l.plt.ylabel(ylabel)
    for patch in patches[1].patches:
        patch.set_hatch('/')
    d2l.plt.legend(legend)

show_list_len_pair_hist(['source', 'target'], '# tokens per sequence',
                        'count', source, target);
../_images/output_machine-translation-and-dataset_887557_54_0.svg
#@save
def show_list_len_pair_hist(legend, xlabel, ylabel, xlist, ylist):
    """Liste uzunluğu çiftleri için histogramı çizin."""
    d2l.set_figsize()
    _, _, patches = d2l.plt.hist(
        [[len(l) for l in xlist], [len(l) for l in ylist]])
    d2l.plt.xlabel(xlabel)
    d2l.plt.ylabel(ylabel)
    for patch in patches[1].patches:
        patch.set_hatch('/')
    d2l.plt.legend(legend)

show_list_len_pair_hist(['source', 'target'], '# tokens per sequence',
                        'count', source, target);
../_images/output_machine-translation-and-dataset_887557_57_0.svg

9.5.3. Kelime Dağarcığı

Makine çeviri veri kümesi dil çiftlerinden oluştuğundan, hem kaynak dil hem de hedef dil için ayrı ayrı iki kelime hazinesi oluşturabiliriz. Kelime düzeyinde andıçlamada, kelime dağarcığı boyutu, karakter düzeyinde andıç kullanandan önemli ölçüde daha büyük olacaktır. Bunu hafifletmek için, burada 2 defadan az görünen seyrek andıçları aynı bilinmeyen (“<unk>”) andıcı ile ifade ediyoruz. Bunun yanı sıra, minigruplarda dizileri aynı uzunlukta dolgulamak için (“<pad>”) ve dizilerin başlangıcını işaretlemek için (“<bos>”) veya sonunu işaretlemek için (“<eos>”) gibi ek özel andıçlar belirtiyoruz. Bu tür özel andıçlar, doğal dil işleme görevlerinde yaygın olarak kullanılır.

src_vocab = d2l.Vocab(source, min_freq=2,
                      reserved_tokens=['<pad>', '<bos>', '<eos>'])
len(src_vocab)
10012
src_vocab = d2l.Vocab(source, min_freq=2,
                      reserved_tokens=['<pad>', '<bos>', '<eos>'])
len(src_vocab)
10012
src_vocab = d2l.Vocab(source, min_freq=2,
                      reserved_tokens=['<pad>', '<bos>', '<eos>'])
len(src_vocab)
10012

9.5.4. Veri Kümesini Okuma

Dil modellemesinde, her dizi örneğinin, bir cümlenin bir kesimine veya birden fazla cümle üzerine bir yayılan sabit bir uzunluğa sahip olduğunu hatırlayın. Bu Section 8.3 içindeki num_steps (zaman adımları veya andıç sayısı) bağımsız değişkeni tarafından belirtilmiştir. Makine çevirisinde, her örnek, her metin dizisinin farklı uzunluklara sahip olabileceği bir kaynak ve hedef metin dizisi çiftidir.

Hesaplamada verimlilik için, yine de bir minigrup metin dizisini kırkma (truncation) ve dolgu ile işleyebiliriz. Aynı minigruptaki her dizinin aynı num_steps uzunluğunda olması gerektiğini varsayalım. Bir metin dizisi num_steps andıçtan daha azsa, uzunluğu num_steps’e ulaşana kadar özel “<pad>” andıcını sonuna eklemeye devam edeceğiz. Aksi takdirde, metin sırasını yalnızca ilk num_steps andıcını alıp geri kalanını atarak keseceğiz. Bu şekilde, her metin dizisi aynı şekle sahip minigruplar olarak yüklenebileceği aynı uzunluğa sahip olacaktır.

Aşağıdaki truncate_pad işlevi metin dizilerini daha önce açıklandığı gibi keser veya dolgular.

#@save
def truncate_pad(line, num_steps, padding_token):
    """Dizileri dolgula veya kırk"""
    if len(line) > num_steps:
        return line[:num_steps]  # Truncate
    return line + [padding_token] * (num_steps - len(line))  # Pad

truncate_pad(src_vocab[source[0]], 10, src_vocab['<pad>'])
[47, 4, 1, 1, 1, 1, 1, 1, 1, 1]
#@save
def truncate_pad(line, num_steps, padding_token):
    """Dizileri dolgula veya kırk"""
    if len(line) > num_steps:
        return line[:num_steps]  # Truncate
    return line + [padding_token] * (num_steps - len(line))  # Pad

truncate_pad(src_vocab[source[0]], 10, src_vocab['<pad>'])
[47, 4, 1, 1, 1, 1, 1, 1, 1, 1]
#@save
def truncate_pad(line, num_steps, padding_token):
    """Dizileri dolgula veya kırk"""
    if len(line) > num_steps:
        return line[:num_steps]  # Truncate
    return line + [padding_token] * (num_steps - len(line))  # Pad

truncate_pad(src_vocab[source[0]], 10, src_vocab['<pad>'])
[47, 4, 1, 1, 1, 1, 1, 1, 1, 1]

Şimdi, metin dizilerini eğitimde minigruplara dönüştürmek için bir işlev tanımlıyoruz. Dizinin sonunu belirtmek için her dizinin sonuna özel “<eos>” andıcını ekliyoruz. Bir model bir diziyi her andıç sonrası bir andıç oluşturarak tahmin ettiğinde, modelin “<eos>” andıcını oluşturması çıktı dizisini tamamlandığını ifade edebilir. Ayrıca, dolgu andıçlarını hariç tutarak her metin dizisinin uzunluğunu da kaydediyoruz. Bu bilgi, daha sonra ele alacağımız bazı modellerde gerekli olacaktır.

#@save
def build_array_nmt(lines, vocab, num_steps):
    """Makine çevirisinin metin dizilerini minigruplara dönüştürün."""
    lines = [vocab[l] for l in lines]
    lines = [l + [vocab['<eos>']] for l in lines]
    array = np.array([truncate_pad(
        l, num_steps, vocab['<pad>']) for l in lines])
    valid_len = (array != vocab['<pad>']).astype(np.int32).sum(1)
    return array, valid_len
#@save
def build_array_nmt(lines, vocab, num_steps):
    """Makine çevirisinin metin dizilerini minigruplara dönüştürün."""
    lines = [vocab[l] for l in lines]
    lines = [l + [vocab['<eos>']] for l in lines]
    array = torch.tensor([truncate_pad(
        l, num_steps, vocab['<pad>']) for l in lines])
    valid_len = (array != vocab['<pad>']).type(torch.int32).sum(1)
    return array, valid_len
#@save
def build_array_nmt(lines, vocab, num_steps):
    """Makine çevirisinin metin dizilerini minigruplara dönüştürün."""
    lines = [vocab[l] for l in lines]
    lines = [l + [vocab['<eos>']] for l in lines]
    array = tf.constant([truncate_pad(
        l, num_steps, vocab['<pad>']) for l in lines])
    valid_len = tf.reduce_sum(
        tf.cast(array != vocab['<pad>'], tf.int32), 1)
    return array, valid_len

9.5.5. Her Şeyi Bir Araya Koyma

Son olarak, veri yineleyiciyi hem kaynak dil hem de hedef dil için kelime dağarcıkları ile birlikte döndüren load_data_nmt işlevini tanımlıyoruz.

#@save
def load_data_nmt(batch_size, num_steps, num_examples=600):
    """Çeviri veri kümesinin yineleyicisini ve sözcük dağarcıklarını döndür."""
    text = preprocess_nmt(read_data_nmt())
    source, target = tokenize_nmt(text, num_examples)
    src_vocab = d2l.Vocab(source, min_freq=2,
                          reserved_tokens=['<pad>', '<bos>', '<eos>'])
    tgt_vocab = d2l.Vocab(target, min_freq=2,
                          reserved_tokens=['<pad>', '<bos>', '<eos>'])
    src_array, src_valid_len = build_array_nmt(source, src_vocab, num_steps)
    tgt_array, tgt_valid_len = build_array_nmt(target, tgt_vocab, num_steps)
    data_arrays = (src_array, src_valid_len, tgt_array, tgt_valid_len)
    data_iter = d2l.load_array(data_arrays, batch_size)
    return data_iter, src_vocab, tgt_vocab

İngilizce-Fransız veri kümesinden ilk minigrubu okuyalım.

train_iter, src_vocab, tgt_vocab = load_data_nmt(batch_size=2, num_steps=8)
for X, X_valid_len, Y, Y_valid_len in train_iter:
    print('X:', X.astype(np.int32))
    print('valid lengths for X:', X_valid_len)
    print('Y:', Y.astype(np.int32))
    print('valid lengths for Y:', Y_valid_len)
    break
X: [[76  8  5  3  1  1  1  1]
 [ 9 72  4  3  1  1  1  1]]
valid lengths for X: [4 4]
Y: [[ 0 28  5  3  1  1  1  1]
 [ 0 55  4  3  1  1  1  1]]
valid lengths for Y: [4 4]
train_iter, src_vocab, tgt_vocab = load_data_nmt(batch_size=2, num_steps=8)
for X, X_valid_len, Y, Y_valid_len in train_iter:
    print('X:', X.type(torch.int32))
    print('valid lengths for X:', X_valid_len)
    print('Y:', Y.type(torch.int32))
    print('valid lengths for Y:', Y_valid_len)
    break
X: tensor([[ 9, 21,  4,  3,  1,  1,  1,  1],
        [ 7, 59,  4,  3,  1,  1,  1,  1]], dtype=torch.int32)
valid lengths for X: tensor([4, 4])
Y: tensor([[97,  4,  3,  1,  1,  1,  1,  1],
        [38,  7,  0,  4,  3,  1,  1,  1]], dtype=torch.int32)
valid lengths for Y: tensor([3, 5])
train_iter, src_vocab, tgt_vocab = load_data_nmt(batch_size=2, num_steps=8)
for X, X_valid_len, Y, Y_valid_len in train_iter:
    print('X:', tf.cast(X, tf.int32))
    print('valid lengths for X:', X_valid_len)
    print('Y:', tf.cast(Y, tf.int32))
    print('valid lengths for Y:', Y_valid_len)
    break
X: tf.Tensor(
[[ 52 176   4   3   1   1   1   1]
 [ 31 168   4   3   1   1   1   1]], shape=(2, 8), dtype=int32)
valid lengths for X: tf.Tensor([4 4], shape=(2,), dtype=int32)
Y: tf.Tensor(
[[30  0 22  5  3  1  1  1]
 [ 0  5  3  1  1  1  1  1]], shape=(2, 8), dtype=int32)
valid lengths for Y: tf.Tensor([5 3], shape=(2,), dtype=int32)

9.5.6. Özet

  • Makine çevirisi, bir dizinin bir dilden diğerine otomatik çevirisini ifade eder.

  • Kelime düzeyinde andıçlama kullanırsak, kelime dağarcığının boyutu, karakter düzeyinde andıçlama kullanmaya göre önemli ölçüde daha büyük olacaktır. Bunu hafifletmek için, seyrek kullanılan andıçları aynı bilinmeyen andıç olarak ifade alabiliriz.

  • Metin dizilerini kırkabilir ve dolgulayabiliriz, böylece hepsi minigruplarda yüklenirken aynı uzunluğa sahip olurlar.

9.5.7. Alıştırmalar

  1. load_data_nmt işlevindeki num_examples değişkeninin farklı değerlerini deneyin. Bu, kaynak ve hedef dillerin kelime dağarcığı boyutlarını nasıl etkiler?

  2. Çince ve Japonca gibi bazı dillerde metin, kelime sınır göstergelerine (örn., boşluk) sahip değildir. Sözcük düzeyinde andıçlama bu gibi durumlar için hala iyi bir fikir midir? Neden?