42 KiB
Modelowanie języka
7. Zanurzenia słów [wykład]
Filip Graliński (2022)
Zanurzenia słów
W praktyce stosowalność słowosieci okazała się zaskakująco ograniczona. Większy przełom w przetwarzaniu języka naturalnego przyniosły wielowymiarowe reprezentacje słów, inaczej: zanurzenia słów.
„Wymiary” słów
Moglibyśmy zanurzyć (ang. _embed) w wielowymiarowej przestrzeni, tzn. zdefiniować odwzorowanie $E \colon V \rightarrow \mathcal{R}^m$ dla pewnego $m$ i określić taki sposób estymowania prawdopodobieństw $P(u|v)$, by dla par $E(v)$ i $E(v')$ oraz $E(u)$ i $E(u')$ znajdujących się w pobliżu (według jakiejś metryki odległości, na przykład zwykłej odległości euklidesowej):
$$P(u|v) \approx P(u'|v').$$
$E(u)$ nazywamy zanurzeniem (embeddingiem) słowa.
Wymiary określone z góry?
Można by sobie wyobrazić, że $m$ wymiarów mogłoby być z góry określonych przez lingwistę. Wymiary te byłyby związane z typowymi „osiami” rozpatrywanymi w językoznawstwie, na przykład:
- czy słowo jest wulgarne, pospolite, potoczne, neutralne czy książkowe?
- czy słowo jest archaiczne, wychodzące z użycia czy jest neologizmem?
- czy słowo dotyczy kobiet, czy mężczyzn (w sensie rodzaju gramatycznego i/lub socjolingwistycznym)?
- czy słowo jest w liczbie pojedynczej czy mnogiej?
- czy słowo jest rzeczownikiem czy czasownikiem?
- czy słowo jest rdzennym słowem czy zapożyczeniem?
- czy słowo jest nazwą czy słowem pospolitym?
- czy słowo opisuje konkretną rzecz czy pojęcie abstrakcyjne?
- …
W praktyce okazało się jednak, że lepiej, żeby komputer uczył się sam możliwych wymiarów — z góry określamy tylko $m$ (liczbę wymiarów).
Bigramowy model języka oparty na zanurzeniach
Zbudujemy teraz najprostszy model język oparty na zanurzeniach. Będzie to właściwie najprostszy neuronowy model języka, jako że zbudowany model można traktować jako prostą sieć neuronową.
Słownik
W typowym neuronowym modelu języka rozmiar słownika musi być z góry
ograniczony. Zazwyczaj jest to liczba rzędu kilkudziesięciu wyrazów —
po prostu będziemy rozpatrywać $|V|$ najczęstszych wyrazów, pozostałe zamienimy
na specjalny token <unk>
reprezentujący nieznany (_unknown) wyraz.
Aby utworzyć taki słownik użyjemy gotowej klasy Vocab
z pakietu torchtext:
from itertools import islice
import regex as re
import sys
from torchtext.vocab import build_vocab_from_iterator
def get_words_from_line(line):
line = line.rstrip()
yield '<s>'
for m in re.finditer(r'[\p{L}0-9\*]+|\p{P}+', line):
yield m.group(0).lower()
yield '</s>'
def get_word_lines_from_file(file_name):
with open(file_name, 'r') as fh:
for line in fh:
yield get_words_from_line(line)
vocab_size = 20000
vocab = build_vocab_from_iterator(
get_word_lines_from_file('opensubtitlesA.pl.txt'),
max_tokens = vocab_size,
specials = ['<unk>'])
vocab['jest']
/media/kuba/ssdsam/anaconda3/envs/lmzajecia/lib/python3.10/site-packages/torch/_masked/__init__.py:223: UserWarning: Failed to initialize NumPy: No module named 'numpy' (Triggered internally at /opt/conda/conda-bld/pytorch_1646755897462/work/torch/csrc/utils/tensor_numpy.cpp:68.) example_input = torch.tensor([[-3, -2, -1], [0, 1, 2]])
16
vocab.lookup_tokens([0, 1, 2, 10, 12345])
['<unk>', '</s>', '<s>', 'w', 'policyjny']
Definicja sieci
Naszą prostą sieć neuronową zaimplementujemy używając frameworku PyTorch.
from torch import nn
import torch
embed_size = 100
class SimpleBigramNeuralLanguageModel(nn.Module):
def __init__(self, vocabulary_size, embedding_size):
super(SimpleBigramNeuralLanguageModel, self).__init__()
self.model = nn.Sequential(
nn.Embedding(vocabulary_size, embedding_size),
nn.Linear(embedding_size, vocabulary_size),
nn.Softmax()
)
def forward(self, x):
return self.model(x)
model = SimpleBigramNeuralLanguageModel(vocab_size, embed_size)
vocab.set_default_index(vocab['<unk>'])
ixs = torch.tensor(vocab.forward(['pies']))
out[0][vocab['jest']]
[0;31m---------------------------------------------------------------------------[0m [0;31mNameError[0m Traceback (most recent call last) Input [0;32mIn [4][0m, in [0;36m<cell line: 22>[0;34m()[0m [1;32m 20[0m vocab[38;5;241m.[39mset_default_index(vocab[[38;5;124m'[39m[38;5;124m<unk>[39m[38;5;124m'[39m]) [1;32m 21[0m ixs [38;5;241m=[39m torch[38;5;241m.[39mtensor(vocab[38;5;241m.[39mforward([[38;5;124m'[39m[38;5;124mpies[39m[38;5;124m'[39m])) [0;32m---> 22[0m [43mout[49m[[38;5;241m0[39m][vocab[[38;5;124m'[39m[38;5;124mjest[39m[38;5;124m'[39m]] [0;31mNameError[0m: name 'out' is not defined
Teraz wyuczmy model. Wpierw tylko potasujmy nasz plik:
shuf < opensubtitlesA.pl.txt > opensubtitlesA.pl.shuf.txt
!shuf < opensubtitlesA.pl.txt > opensubtitlesA.pl.shuf.txt
from torch.utils.data import IterableDataset
import itertools
def look_ahead_iterator(gen):
prev = None
for item in gen:
if prev is not None:
yield (prev, item)
prev = item
class Bigrams(IterableDataset):
def __init__(self, text_file, vocabulary_size):
self.vocab = build_vocab_from_iterator(
get_word_lines_from_file(text_file),
max_tokens = vocabulary_size,
specials = ['<unk>'])
self.vocab.set_default_index(self.vocab['<unk>'])
self.vocabulary_size = vocabulary_size
self.text_file = text_file
def __iter__(self):
return look_ahead_iterator(
(self.vocab[t] for t in itertools.chain.from_iterable(get_word_lines_from_file(self.text_file))))
train_dataset = Bigrams('opensubtitlesA.pl.shuf.txt', vocab_size)
from torch.utils.data import DataLoader
next(iter(train_dataset))
(2, 72)
from torch.utils.data import DataLoader
next(iter(DataLoader(train_dataset, batch_size=5)))
[tensor([ 2, 72, 615, 11, 92]), tensor([ 72, 615, 11, 92, 4])]
device = 'cuda'
model = SimpleBigramNeuralLanguageModel(vocab_size, embed_size).to(device)
data = DataLoader(train_dataset, batch_size=5000)
optimizer = torch.optim.Adam(model.parameters())
criterion = torch.nn.NLLLoss()
model.train()
step = 0
for x, y in data:
x = x.to(device)
y = y.to(device)
optimizer.zero_grad()
ypredicted = model(x)
loss = criterion(torch.log(ypredicted), y)
if step % 100 == 0:
print(step, loss)
step += 1
loss.backward()
optimizer.step()
torch.save(model.state_dict(), 'model1.bin')
#Policzmy najbardziej prawdopodobne kontynuację dla zadanego słowa:
/media/kuba/ssdsam/anaconda3/envs/lmzajecia/lib/python3.10/site-packages/torch/nn/modules/container.py:141: UserWarning: Implicit dimension choice for softmax has been deprecated. Change the call to include dim=X as an argument. input = module(input)
0 tensor(10.2158, device='cuda:0', grad_fn=<NllLossBackward0>) 100 tensor(6.9743, device='cuda:0', grad_fn=<NllLossBackward0>) 200 tensor(6.2186, device='cuda:0', grad_fn=<NllLossBackward0>) 300 tensor(5.6430, device='cuda:0', grad_fn=<NllLossBackward0>) 400 tensor(5.3539, device='cuda:0', grad_fn=<NllLossBackward0>) 500 tensor(5.0689, device='cuda:0', grad_fn=<NllLossBackward0>) 600 tensor(4.9418, device='cuda:0', grad_fn=<NllLossBackward0>) 700 tensor(4.8142, device='cuda:0', grad_fn=<NllLossBackward0>) 800 tensor(4.6436, device='cuda:0', grad_fn=<NllLossBackward0>) 900 tensor(4.6770, device='cuda:0', grad_fn=<NllLossBackward0>) 1000 tensor(4.6069, device='cuda:0', grad_fn=<NllLossBackward0>) 1100 tensor(4.5514, device='cuda:0', grad_fn=<NllLossBackward0>) 1200 tensor(4.5288, device='cuda:0', grad_fn=<NllLossBackward0>) 1300 tensor(4.4578, device='cuda:0', grad_fn=<NllLossBackward0>) 1400 tensor(4.5290, device='cuda:0', grad_fn=<NllLossBackward0>) 1500 tensor(4.5229, device='cuda:0', grad_fn=<NllLossBackward0>) 1600 tensor(4.4973, device='cuda:0', grad_fn=<NllLossBackward0>) 1700 tensor(4.3793, device='cuda:0', grad_fn=<NllLossBackward0>) 1800 tensor(4.5056, device='cuda:0', grad_fn=<NllLossBackward0>) 1900 tensor(4.3709, device='cuda:0', grad_fn=<NllLossBackward0>) 2000 tensor(4.3841, device='cuda:0', grad_fn=<NllLossBackward0>) 2100 tensor(4.4515, device='cuda:0', grad_fn=<NllLossBackward0>) 2200 tensor(4.3367, device='cuda:0', grad_fn=<NllLossBackward0>) 2300 tensor(4.4187, device='cuda:0', grad_fn=<NllLossBackward0>) 2400 tensor(4.3672, device='cuda:0', grad_fn=<NllLossBackward0>) 2500 tensor(4.3117, device='cuda:0', grad_fn=<NllLossBackward0>) 2600 tensor(4.2908, device='cuda:0', grad_fn=<NllLossBackward0>) 2700 tensor(4.3188, device='cuda:0', grad_fn=<NllLossBackward0>) 2800 tensor(4.2870, device='cuda:0', grad_fn=<NllLossBackward0>) 2900 tensor(4.2855, device='cuda:0', grad_fn=<NllLossBackward0>) 3000 tensor(4.2927, device='cuda:0', grad_fn=<NllLossBackward0>) 3100 tensor(4.3358, device='cuda:0', grad_fn=<NllLossBackward0>) 3200 tensor(4.2719, device='cuda:0', grad_fn=<NllLossBackward0>) 3300 tensor(4.2606, device='cuda:0', grad_fn=<NllLossBackward0>) 3400 tensor(4.2953, device='cuda:0', grad_fn=<NllLossBackward0>) 3500 tensor(4.3175, device='cuda:0', grad_fn=<NllLossBackward0>) 3600 tensor(4.2448, device='cuda:0', grad_fn=<NllLossBackward0>) 3700 tensor(4.2430, device='cuda:0', grad_fn=<NllLossBackward0>) 3800 tensor(4.2586, device='cuda:0', grad_fn=<NllLossBackward0>) 3900 tensor(4.2905, device='cuda:0', grad_fn=<NllLossBackward0>) 4000 tensor(4.2455, device='cuda:0', grad_fn=<NllLossBackward0>) 4100 tensor(4.2214, device='cuda:0', grad_fn=<NllLossBackward0>) 4200 tensor(4.2325, device='cuda:0', grad_fn=<NllLossBackward0>) 4300 tensor(4.3036, device='cuda:0', grad_fn=<NllLossBackward0>) 4400 tensor(4.2335, device='cuda:0', grad_fn=<NllLossBackward0>) 4500 tensor(4.2377, device='cuda:0', grad_fn=<NllLossBackward0>) 4600 tensor(4.2109, device='cuda:0', grad_fn=<NllLossBackward0>) 4700 tensor(4.2942, device='cuda:0', grad_fn=<NllLossBackward0>) 4800 tensor(4.2234, device='cuda:0', grad_fn=<NllLossBackward0>) 4900 tensor(4.1918, device='cuda:0', grad_fn=<NllLossBackward0>) 5000 tensor(4.3084, device='cuda:0', grad_fn=<NllLossBackward0>) 5100 tensor(4.1666, device='cuda:0', grad_fn=<NllLossBackward0>) 5200 tensor(4.2307, device='cuda:0', grad_fn=<NllLossBackward0>) 5300 tensor(4.2050, device='cuda:0', grad_fn=<NllLossBackward0>) 5400 tensor(4.1853, device='cuda:0', grad_fn=<NllLossBackward0>) 5500 tensor(4.1917, device='cuda:0', grad_fn=<NllLossBackward0>) 5600 tensor(4.1453, device='cuda:0', grad_fn=<NllLossBackward0>) 5700 tensor(4.2423, device='cuda:0', grad_fn=<NllLossBackward0>) 5800 tensor(4.1972, device='cuda:0', grad_fn=<NllLossBackward0>) 5900 tensor(4.2143, device='cuda:0', grad_fn=<NllLossBackward0>) 6000 tensor(4.2172, device='cuda:0', grad_fn=<NllLossBackward0>) 6100 tensor(4.2463, device='cuda:0', grad_fn=<NllLossBackward0>) 6200 tensor(4.1756, device='cuda:0', grad_fn=<NllLossBackward0>) 6300 tensor(4.1223, device='cuda:0', grad_fn=<NllLossBackward0>) 6400 tensor(4.1852, device='cuda:0', grad_fn=<NllLossBackward0>) 6500 tensor(4.1559, device='cuda:0', grad_fn=<NllLossBackward0>) 6600 tensor(4.1833, device='cuda:0', grad_fn=<NllLossBackward0>) 6700 tensor(4.2090, device='cuda:0', grad_fn=<NllLossBackward0>) 6800 tensor(4.1896, device='cuda:0', grad_fn=<NllLossBackward0>) 6900 tensor(4.2057, device='cuda:0', grad_fn=<NllLossBackward0>) 7000 tensor(4.1523, device='cuda:0', grad_fn=<NllLossBackward0>) 7100 tensor(4.2645, device='cuda:0', grad_fn=<NllLossBackward0>) 7200 tensor(4.1974, device='cuda:0', grad_fn=<NllLossBackward0>) 7300 tensor(4.2031, device='cuda:0', grad_fn=<NllLossBackward0>) 7400 tensor(4.1613, device='cuda:0', grad_fn=<NllLossBackward0>) 7500 tensor(4.2018, device='cuda:0', grad_fn=<NllLossBackward0>) 7600 tensor(4.2197, device='cuda:0', grad_fn=<NllLossBackward0>) 7700 tensor(4.1976, device='cuda:0', grad_fn=<NllLossBackward0>) 7800 tensor(4.1650, device='cuda:0', grad_fn=<NllLossBackward0>) 7900 tensor(4.1380, device='cuda:0', grad_fn=<NllLossBackward0>) 8000 tensor(4.1014, device='cuda:0', grad_fn=<NllLossBackward0>) 8100 tensor(4.2058, device='cuda:0', grad_fn=<NllLossBackward0>) 8200 tensor(4.1514, device='cuda:0', grad_fn=<NllLossBackward0>) 8300 tensor(4.1187, device='cuda:0', grad_fn=<NllLossBackward0>) 8400 tensor(4.2438, device='cuda:0', grad_fn=<NllLossBackward0>) 8500 tensor(4.2094, device='cuda:0', grad_fn=<NllLossBackward0>) 8600 tensor(4.2077, device='cuda:0', grad_fn=<NllLossBackward0>) 8700 tensor(4.0819, device='cuda:0', grad_fn=<NllLossBackward0>) 8800 tensor(4.1766, device='cuda:0', grad_fn=<NllLossBackward0>) 8900 tensor(4.1805, device='cuda:0', grad_fn=<NllLossBackward0>) 9000 tensor(4.1847, device='cuda:0', grad_fn=<NllLossBackward0>) 9100 tensor(4.1929, device='cuda:0', grad_fn=<NllLossBackward0>) 9200 tensor(4.1434, device='cuda:0', grad_fn=<NllLossBackward0>) 9300 tensor(4.1678, device='cuda:0', grad_fn=<NllLossBackward0>) 9400 tensor(4.1699, device='cuda:0', grad_fn=<NllLossBackward0>) 9500 tensor(4.0885, device='cuda:0', grad_fn=<NllLossBackward0>) 9600 tensor(4.1544, device='cuda:0', grad_fn=<NllLossBackward0>) 9700 tensor(4.1828, device='cuda:0', grad_fn=<NllLossBackward0>) 9800 tensor(4.1314, device='cuda:0', grad_fn=<NllLossBackward0>) 9900 tensor(4.1473, device='cuda:0', grad_fn=<NllLossBackward0>) 10000 tensor(4.0948, device='cuda:0', grad_fn=<NllLossBackward0>) 10100 tensor(4.1396, device='cuda:0', grad_fn=<NllLossBackward0>) 10200 tensor(4.1999, device='cuda:0', grad_fn=<NllLossBackward0>) 10300 tensor(4.1027, device='cuda:0', grad_fn=<NllLossBackward0>) 10400 tensor(4.2049, device='cuda:0', grad_fn=<NllLossBackward0>) 10500 tensor(4.1470, device='cuda:0', grad_fn=<NllLossBackward0>) 10600 tensor(4.0974, device='cuda:0', grad_fn=<NllLossBackward0>) 10700 tensor(4.1239, device='cuda:0', grad_fn=<NllLossBackward0>) 10800 tensor(4.1381, device='cuda:0', grad_fn=<NllLossBackward0>) 10900 tensor(4.0569, device='cuda:0', grad_fn=<NllLossBackward0>) 11000 tensor(4.1138, device='cuda:0', grad_fn=<NllLossBackward0>) 11100 tensor(4.2053, device='cuda:0', grad_fn=<NllLossBackward0>) 11200 tensor(4.1404, device='cuda:0', grad_fn=<NllLossBackward0>) 11300 tensor(4.0741, device='cuda:0', grad_fn=<NllLossBackward0>) 11400 tensor(4.0090, device='cuda:0', grad_fn=<NllLossBackward0>) 11500 tensor(4.1568, device='cuda:0', grad_fn=<NllLossBackward0>) 11600 tensor(4.1498, device='cuda:0', grad_fn=<NllLossBackward0>) 11700 tensor(4.1052, device='cuda:0', grad_fn=<NllLossBackward0>) 11800 tensor(4.0600, device='cuda:0', grad_fn=<NllLossBackward0>) 11900 tensor(4.1274, device='cuda:0', grad_fn=<NllLossBackward0>) 12000 tensor(4.1346, device='cuda:0', grad_fn=<NllLossBackward0>) 12100 tensor(4.1024, device='cuda:0', grad_fn=<NllLossBackward0>) 12200 tensor(4.0966, device='cuda:0', grad_fn=<NllLossBackward0>) 12300 tensor(4.1036, device='cuda:0', grad_fn=<NllLossBackward0>) 12400 tensor(4.0127, device='cuda:0', grad_fn=<NllLossBackward0>) 12500 tensor(4.0575, device='cuda:0', grad_fn=<NllLossBackward0>) 12600 tensor(4.1542, device='cuda:0', grad_fn=<NllLossBackward0>) 12700 tensor(4.1810, device='cuda:0', grad_fn=<NllLossBackward0>) 12800 tensor(4.1948, device='cuda:0', grad_fn=<NllLossBackward0>) 12900 tensor(4.1085, device='cuda:0', grad_fn=<NllLossBackward0>) 13000 tensor(4.1283, device='cuda:0', grad_fn=<NllLossBackward0>) 13100 tensor(4.1548, device='cuda:0', grad_fn=<NllLossBackward0>) 13200 tensor(4.1015, device='cuda:0', grad_fn=<NllLossBackward0>) 13300 tensor(4.1342, device='cuda:0', grad_fn=<NllLossBackward0>) 13400 tensor(4.0724, device='cuda:0', grad_fn=<NllLossBackward0>) 13500 tensor(4.1006, device='cuda:0', grad_fn=<NllLossBackward0>) 13600 tensor(4.0998, device='cuda:0', grad_fn=<NllLossBackward0>) 13700 tensor(4.1021, device='cuda:0', grad_fn=<NllLossBackward0>) 13800 tensor(4.1175, device='cuda:0', grad_fn=<NllLossBackward0>) 13900 tensor(4.1017, device='cuda:0', grad_fn=<NllLossBackward0>) 14000 tensor(4.1877, device='cuda:0', grad_fn=<NllLossBackward0>) 14100 tensor(4.1664, device='cuda:0', grad_fn=<NllLossBackward0>) 14200 tensor(4.1582, device='cuda:0', grad_fn=<NllLossBackward0>) 14300 tensor(4.1526, device='cuda:0', grad_fn=<NllLossBackward0>) 14400 tensor(4.1208, device='cuda:0', grad_fn=<NllLossBackward0>) 14500 tensor(4.0752, device='cuda:0', grad_fn=<NllLossBackward0>) 14600 tensor(4.1907, device='cuda:0', grad_fn=<NllLossBackward0>) 14700 tensor(4.0496, device='cuda:0', grad_fn=<NllLossBackward0>) 14800 tensor(4.1371, device='cuda:0', grad_fn=<NllLossBackward0>) 14900 tensor(4.1215, device='cuda:0', grad_fn=<NllLossBackward0>) 15000 tensor(4.1059, device='cuda:0', grad_fn=<NllLossBackward0>) 15100 tensor(4.0888, device='cuda:0', grad_fn=<NllLossBackward0>) 15200 tensor(4.1359, device='cuda:0', grad_fn=<NllLossBackward0>) 15300 tensor(4.1328, device='cuda:0', grad_fn=<NllLossBackward0>) 15400 tensor(4.1044, device='cuda:0', grad_fn=<NllLossBackward0>) 15500 tensor(4.1167, device='cuda:0', grad_fn=<NllLossBackward0>) 15600 tensor(4.0449, device='cuda:0', grad_fn=<NllLossBackward0>) 15700 tensor(4.1159, device='cuda:0', grad_fn=<NllLossBackward0>) 15800 tensor(4.1082, device='cuda:0', grad_fn=<NllLossBackward0>) 15900 tensor(4.1653, device='cuda:0', grad_fn=<NllLossBackward0>) 16000 tensor(4.1111, device='cuda:0', grad_fn=<NllLossBackward0>) 16100 tensor(4.0870, device='cuda:0', grad_fn=<NllLossBackward0>) 16200 tensor(4.1085, device='cuda:0', grad_fn=<NllLossBackward0>) 16300 tensor(4.1216, device='cuda:0', grad_fn=<NllLossBackward0>) 16400 tensor(4.1307, device='cuda:0', grad_fn=<NllLossBackward0>) 16500 tensor(4.0872, device='cuda:0', grad_fn=<NllLossBackward0>) 16600 tensor(4.0754, device='cuda:0', grad_fn=<NllLossBackward0>) 16700 tensor(4.0067, device='cuda:0', grad_fn=<NllLossBackward0>) 16800 tensor(4.0413, device='cuda:0', grad_fn=<NllLossBackward0>) 16900 tensor(4.1242, device='cuda:0', grad_fn=<NllLossBackward0>) 17000 tensor(4.1169, device='cuda:0', grad_fn=<NllLossBackward0>) 17100 tensor(4.0942, device='cuda:0', grad_fn=<NllLossBackward0>) 17200 tensor(4.1518, device='cuda:0', grad_fn=<NllLossBackward0>) 17300 tensor(4.0968, device='cuda:0', grad_fn=<NllLossBackward0>) 17400 tensor(4.0476, device='cuda:0', grad_fn=<NllLossBackward0>) 17500 tensor(4.0230, device='cuda:0', grad_fn=<NllLossBackward0>) 17600 tensor(4.1268, device='cuda:0', grad_fn=<NllLossBackward0>) 17700 tensor(4.0388, device='cuda:0', grad_fn=<NllLossBackward0>) 17800 tensor(4.1741, device='cuda:0', grad_fn=<NllLossBackward0>) 17900 tensor(4.1147, device='cuda:0', grad_fn=<NllLossBackward0>) 18000 tensor(4.2020, device='cuda:0', grad_fn=<NllLossBackward0>) 18100 tensor(4.0304, device='cuda:0', grad_fn=<NllLossBackward0>) 18200 tensor(4.1171, device='cuda:0', grad_fn=<NllLossBackward0>) 18300 tensor(4.0945, device='cuda:0', grad_fn=<NllLossBackward0>) 18400 tensor(4.1019, device='cuda:0', grad_fn=<NllLossBackward0>) 18500 tensor(4.1301, device='cuda:0', grad_fn=<NllLossBackward0>) 18600 tensor(4.0979, device='cuda:0', grad_fn=<NllLossBackward0>) 18700 tensor(4.0755, device='cuda:0', grad_fn=<NllLossBackward0>) 18800 tensor(4.0760, device='cuda:0', grad_fn=<NllLossBackward0>) 18900 tensor(4.0553, device='cuda:0', grad_fn=<NllLossBackward0>) 19000 tensor(4.1530, device='cuda:0', grad_fn=<NllLossBackward0>) 19100 tensor(4.1403, device='cuda:0', grad_fn=<NllLossBackward0>) 19200 tensor(4.1449, device='cuda:0', grad_fn=<NllLossBackward0>) 19300 tensor(4.0105, device='cuda:0', grad_fn=<NllLossBackward0>) 19400 tensor(4.0742, device='cuda:0', grad_fn=<NllLossBackward0>) 19500 tensor(4.0666, device='cuda:0', grad_fn=<NllLossBackward0>) 19600 tensor(4.1549, device='cuda:0', grad_fn=<NllLossBackward0>) 19700 tensor(4.0930, device='cuda:0', grad_fn=<NllLossBackward0>) 19800 tensor(4.1271, device='cuda:0', grad_fn=<NllLossBackward0>) 19900 tensor(4.1169, device='cuda:0', grad_fn=<NllLossBackward0>) 20000 tensor(4.1053, device='cuda:0', grad_fn=<NllLossBackward0>) 20100 tensor(4.1070, device='cuda:0', grad_fn=<NllLossBackward0>) 20200 tensor(4.0848, device='cuda:0', grad_fn=<NllLossBackward0>) 20300 tensor(4.1330, device='cuda:0', grad_fn=<NllLossBackward0>) 20400 tensor(3.9828, device='cuda:0', grad_fn=<NllLossBackward0>) 20500 tensor(4.1411, device='cuda:0', grad_fn=<NllLossBackward0>) 20600 tensor(4.0537, device='cuda:0', grad_fn=<NllLossBackward0>) 20700 tensor(4.1171, device='cuda:0', grad_fn=<NllLossBackward0>) 20800 tensor(4.0510, device='cuda:0', grad_fn=<NllLossBackward0>) 20900 tensor(4.1230, device='cuda:0', grad_fn=<NllLossBackward0>) 21000 tensor(4.1241, device='cuda:0', grad_fn=<NllLossBackward0>) 21100 tensor(4.1600, device='cuda:0', grad_fn=<NllLossBackward0>) 21200 tensor(4.0699, device='cuda:0', grad_fn=<NllLossBackward0>) 21300 tensor(4.0870, device='cuda:0', grad_fn=<NllLossBackward0>) 21400 tensor(4.0774, device='cuda:0', grad_fn=<NllLossBackward0>) 21500 tensor(4.1492, device='cuda:0', grad_fn=<NllLossBackward0>) 21600 tensor(4.0883, device='cuda:0', grad_fn=<NllLossBackward0>) 21700 tensor(4.0358, device='cuda:0', grad_fn=<NllLossBackward0>) 21800 tensor(4.0569, device='cuda:0', grad_fn=<NllLossBackward0>) 21900 tensor(4.0832, device='cuda:0', grad_fn=<NllLossBackward0>) 22000 tensor(4.0827, device='cuda:0', grad_fn=<NllLossBackward0>) 22100 tensor(4.0534, device='cuda:0', grad_fn=<NllLossBackward0>) 22200 tensor(4.0173, device='cuda:0', grad_fn=<NllLossBackward0>) 22300 tensor(4.0549, device='cuda:0', grad_fn=<NllLossBackward0>) 22400 tensor(4.0613, device='cuda:0', grad_fn=<NllLossBackward0>) 22500 tensor(4.1058, device='cuda:0', grad_fn=<NllLossBackward0>) 22600 tensor(4.1230, device='cuda:0', grad_fn=<NllLossBackward0>) 22700 tensor(4.1114, device='cuda:0', grad_fn=<NllLossBackward0>) 22800 tensor(4.0541, device='cuda:0', grad_fn=<NllLossBackward0>) 22900 tensor(4.0732, device='cuda:0', grad_fn=<NllLossBackward0>) 23000 tensor(4.0983, device='cuda:0', grad_fn=<NllLossBackward0>) 23100 tensor(4.0547, device='cuda:0', grad_fn=<NllLossBackward0>) 23200 tensor(4.1198, device='cuda:0', grad_fn=<NllLossBackward0>) 23300 tensor(4.0687, device='cuda:0', grad_fn=<NllLossBackward0>) 23400 tensor(4.0676, device='cuda:0', grad_fn=<NllLossBackward0>) 23500 tensor(4.0834, device='cuda:0', grad_fn=<NllLossBackward0>) 23600 tensor(4.0996, device='cuda:0', grad_fn=<NllLossBackward0>) 23700 tensor(4.0791, device='cuda:0', grad_fn=<NllLossBackward0>) 23800 tensor(4.0700, device='cuda:0', grad_fn=<NllLossBackward0>) 23900 tensor(4.0388, device='cuda:0', grad_fn=<NllLossBackward0>) 24000 tensor(4.0625, device='cuda:0', grad_fn=<NllLossBackward0>) 24100 tensor(4.1095, device='cuda:0', grad_fn=<NllLossBackward0>) 24200 tensor(4.1589, device='cuda:0', grad_fn=<NllLossBackward0>) 24300 tensor(4.1565, device='cuda:0', grad_fn=<NllLossBackward0>) 24400 tensor(4.1396, device='cuda:0', grad_fn=<NllLossBackward0>) 24500 tensor(4.1642, device='cuda:0', grad_fn=<NllLossBackward0>) 24600 tensor(4.0868, device='cuda:0', grad_fn=<NllLossBackward0>) 24700 tensor(4.0770, device='cuda:0', grad_fn=<NllLossBackward0>) 24800 tensor(4.0577, device='cuda:0', grad_fn=<NllLossBackward0>) 24900 tensor(4.0662, device='cuda:0', grad_fn=<NllLossBackward0>) 25000 tensor(4.0877, device='cuda:0', grad_fn=<NllLossBackward0>) 25100 tensor(4.0505, device='cuda:0', grad_fn=<NllLossBackward0>) 25200 tensor(4.1542, device='cuda:0', grad_fn=<NllLossBackward0>) 25300 tensor(4.0740, device='cuda:0', grad_fn=<NllLossBackward0>) 25400 tensor(4.0893, device='cuda:0', grad_fn=<NllLossBackward0>) 25500 tensor(4.0370, device='cuda:0', grad_fn=<NllLossBackward0>) 25600 tensor(4.1480, device='cuda:0', grad_fn=<NllLossBackward0>) 25700 tensor(4.1070, device='cuda:0', grad_fn=<NllLossBackward0>) 25800 tensor(4.0381, device='cuda:0', grad_fn=<NllLossBackward0>) 25900 tensor(4.0800, device='cuda:0', grad_fn=<NllLossBackward0>) 26000 tensor(4.0842, device='cuda:0', grad_fn=<NllLossBackward0>) 26100 tensor(4.1127, device='cuda:0', grad_fn=<NllLossBackward0>) 26200 tensor(4.1184, device='cuda:0', grad_fn=<NllLossBackward0>) 26300 tensor(4.0885, device='cuda:0', grad_fn=<NllLossBackward0>) 26400 tensor(4.1423, device='cuda:0', grad_fn=<NllLossBackward0>) 26500 tensor(4.1359, device='cuda:0', grad_fn=<NllLossBackward0>) 26600 tensor(4.0986, device='cuda:0', grad_fn=<NllLossBackward0>) 26700 tensor(4.0580, device='cuda:0', grad_fn=<NllLossBackward0>) 26800 tensor(4.0806, device='cuda:0', grad_fn=<NllLossBackward0>) 26900 tensor(4.0169, device='cuda:0', grad_fn=<NllLossBackward0>) 27000 tensor(4.1111, device='cuda:0', grad_fn=<NllLossBackward0>) 27100 tensor(4.1417, device='cuda:0', grad_fn=<NllLossBackward0>) 27200 tensor(4.1497, device='cuda:0', grad_fn=<NllLossBackward0>) 27300 tensor(4.1093, device='cuda:0', grad_fn=<NllLossBackward0>) 27400 tensor(4.0306, device='cuda:0', grad_fn=<NllLossBackward0>) 27500 tensor(4.1214, device='cuda:0', grad_fn=<NllLossBackward0>) 27600 tensor(4.0745, device='cuda:0', grad_fn=<NllLossBackward0>) 27700 tensor(4.0559, device='cuda:0', grad_fn=<NllLossBackward0>) 27800 tensor(4.0286, device='cuda:0', grad_fn=<NllLossBackward0>) 27900 tensor(4.1266, device='cuda:0', grad_fn=<NllLossBackward0>) 28000 tensor(3.9690, device='cuda:0', grad_fn=<NllLossBackward0>) 28100 tensor(4.1141, device='cuda:0', grad_fn=<NllLossBackward0>) 28200 tensor(4.0565, device='cuda:0', grad_fn=<NllLossBackward0>) 28300 tensor(4.0682, device='cuda:0', grad_fn=<NllLossBackward0>) 28400 tensor(4.0646, device='cuda:0', grad_fn=<NllLossBackward0>) 28500 tensor(4.0386, device='cuda:0', grad_fn=<NllLossBackward0>) 28600 tensor(4.0903, device='cuda:0', grad_fn=<NllLossBackward0>) 28700 tensor(4.1060, device='cuda:0', grad_fn=<NllLossBackward0>)
model
SimpleBigramNeuralLanguageModel( (model): Sequential( (0): Embedding(20000, 100) (1): Linear(in_features=100, out_features=20000, bias=True) (2): Softmax(dim=None) ) )
device = 'cuda'
model = SimpleBigramNeuralLanguageModel(vocab_size, embed_size).to(device)
model.load_state_dict(torch.load('model1.bin'))
model.eval()
ixs = torch.tensor(vocab.forward(['dla'])).to(device)
out = model(ixs)
top = torch.topk(out[0], 10)
top_indices = top.indices.tolist()
top_probs = top.values.tolist()
top_words = vocab.lookup_tokens(top_indices)
list(zip(top_words, top_indices, top_probs))
[('mnie', 26, 0.16004179418087006), ('ciebie', 73, 0.13592898845672607), ('<unk>', 0, 0.12769868969917297), ('nas', 83, 0.04033529385924339), ('niego', 172, 0.033195145428180695), ('niej', 247, 0.021507620811462402), ('was', 162, 0.017743170261383057), ('siebie', 181, 0.01618184894323349), ('nich', 222, 0.01589815877377987), ('pana', 156, 0.014923062175512314)]
Teraz zbadajmy najbardziej podobne zanurzenia dla zadanego słowa:
vocab = train_dataset.vocab
ixs = torch.tensor(vocab.forward(['kłopot'])).to(device)
out = model(ixs)
top = torch.topk(out[0], 10)
top_indices = top.indices.tolist()
top_probs = top.values.tolist()
top_words = vocab.lookup_tokens(top_indices)
list(zip(top_words, top_indices, top_probs))
[('.', 3, 0.3327740728855133), ('z', 14, 0.191472589969635), (',', 4, 0.18250100314617157), ('w', 10, 0.06395534425973892), ('?', 6, 0.059775471687316895), ('i', 11, 0.019332991912961006), ('ze', 60, 0.016418060287833214), ('<unk>', 0, 0.014098692685365677), ('na', 12, 0.01183203887194395), ('...', 15, 0.010537521913647652)]
cos = nn.CosineSimilarity(dim=1, eps=1e-6)
embeddings = model.model[0].weight
vec = embeddings[vocab['poszedł']]
similarities = cos(vec, embeddings)
top = torch.topk(similarities, 10)
top_indices = top.indices.tolist()
top_probs = top.values.tolist()
top_words = vocab.lookup_tokens(top_indices)
list(zip(top_words, top_indices, top_probs))
[('poszedł', 1088, 1.0), ('wsiąść', 9766, 0.46510031819343567), ('pojedzie', 6485, 0.4598822593688965), ('wyjeżdża', 6459, 0.4378735423088074), ('szedłem', 8969, 0.4232063889503479), ('zadzwoniłem', 4889, 0.41752171516418457), ('dotrzemy', 6098, 0.40929487347602844), ('spóźnić', 9923, 0.4015277922153473), ('pójdę', 635, 0.3992091119289398), ('wrócimy', 2070, 0.39785560965538025)]
Zapis przy użyciu wzoru matematycznego
Powyżej zaprogramowaną sieć neuronową można opisać następującym wzorem:
$$\vec{y} = \operatorname{softmax}(CE(w_{i-1}),$$
gdzie:
- $w_{i-1}$ to pierwszy wyraz w bigramie (poprzedzający wyraz),
- $E(w)$ to zanurzenie (embedding) wyrazy $w$ — wektor o rozmiarze $m$,
- $C$ to macierz o rozmiarze $|V| \times m$, która rzutuje wektor zanurzenia w wektor o rozmiarze słownika,
- $\vec{y}$ to wyjściowy wektor prawdopodobieństw o rozmiarze $|V|$.
Hiperparametry
Zauważmy, że nasz model ma dwa hiperparametry:
- $m$ — rozmiar zanurzenia,
- $|V|$ — rozmiar słownika, jeśli zakładamy, że możemy sterować
rozmiarem słownika (np. przez obcinanie słownika do zadanej liczby
najczęstszych wyrazów i zamiany pozostałych na specjalny token, powiedzmy,
<UNK>
.
Oczywiście możemy próbować manipulować wartościami $m$ i $|V|$ w celu polepszenia wyników naszego modelu.
Pytanie: dlaczego nie ma sensu wartość $m \approx |V|$ ? dlaczego nie ma sensu wartość $m = 1$?
Diagram sieci
Jako że mnożenie przez macierz ($C$) oznacza po prostu zastosowanie warstwy liniowej, naszą sieć możemy interpretować jako jednowarstwową sieć neuronową, co można zilustrować za pomocą następującego diagramu:
Zanurzenie jako mnożenie przez macierz
Uzyskanie zanurzenia ($E(w)$) zazwyczaj realizowane jest na zasadzie odpytania (look-up_). Co ciekawe, zanurzenie można intepretować jako mnożenie przez macierz zanurzeń (embeddingów) $E$ o rozmiarze $m \times |V|$ — jeśli słowo będziemy na wejściu kodowali przy użyciu wektora z gorącą jedynką (one-hot encoding_), tzn. słowo $w$ zostanie podane na wejściu jako wektor $\vec{1_V}(w) = [0,\ldots,0,1,0\ldots,0]$ o rozmiarze $|V|$ złożony z samych zer z wyjątkiem jedynki na pozycji odpowiadającej indeksowi wyrazu $w$ w słowniku $V$.
Wówczas wzór przyjmie postać:
$$\vec{y} = \operatorname{softmax}(CE\vec{1_V}(w_{i-1})),$$
gdzie $E$ będzie tym razem macierzą $m \times |V|$.
Pytanie: czy $\vec{1_V}(w)$ intepretujemy jako wektor wierszowy czy kolumnowy?
W postaci diagramu można tę interpretację zilustrować w następujący sposób: