45 KiB
Modelowanie języka
09. Zanurzenia słów (Word2vec) [wykład]
Filip Graliński (2022)
Zanurzenia słów (Word2vec)
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
import lzma
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):
counter=0
with lzma.open(file_name, 'r') as fh:
for line in fh:
counter+=1
if counter == 100000:
break
line = line.decode("utf-8")
yield get_words_from_line(line)
vocab_size = 20000
vocab = build_vocab_from_iterator(
get_word_lines_from_file('train/in.tsv.xz'),
max_tokens = vocab_size,
specials = ['<unk>'])
vocab['jest']
12531
import pickle
with open("vocab.pickle", 'wb') as handle:
pickle.dump(vocab, handle)
with open("vocab.pickle", 'rb') as handle:
vocab = pickle.load( handle)
vocab['love']
838
vocab.lookup_token()
!pip3 install torchtext
WARNING: pip is being invoked by an old script wrapper. This will fail in a future version of pip. Please see https://github.com/pypa/pip/issues/5599 for advice on fixing the underlying issue. To avoid this problem you can invoke Python with '-m pip' instead of running pip directly. Defaulting to user installation because normal site-packages is not writeable Collecting torchtext Downloading torchtext-0.15.1-cp38-cp38-manylinux1_x86_64.whl (2.0 MB) [2K [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m2.0/2.0 MB[0m [31m661.8 kB/s[0m eta [36m0:00:00[0m00:01[0m00:01[0m [?25hRequirement already satisfied: numpy in /home/mikolaj/.local/lib/python3.8/site-packages (from torchtext) (1.19.3) Requirement already satisfied: tqdm in /home/mikolaj/.local/lib/python3.8/site-packages (from torchtext) (4.64.0) Collecting torchdata==0.6.0 Downloading torchdata-0.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.6 MB) [2K [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m4.6/4.6 MB[0m [31m1.1 MB/s[0m eta [36m0:00:00[0m00:01[0m00:01[0m0m [?25hRequirement already satisfied: requests in /home/mikolaj/.local/lib/python3.8/site-packages (from torchtext) (2.26.0) Collecting torch==2.0.0 Downloading torch-2.0.0-cp38-cp38-manylinux1_x86_64.whl (619.9 MB) [2K [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m619.9/619.9 MB[0m [31m2.7 MB/s[0m eta [36m0:00:00[0m00:01[0m00:02[0m [?25hCollecting nvidia-nccl-cu11==2.14.3 Downloading nvidia_nccl_cu11-2.14.3-py3-none-manylinux1_x86_64.whl (177.1 MB) [2K [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m177.1/177.1 MB[0m [31m6.4 MB/s[0m eta [36m0:00:00[0m00:01[0m00:01[0m [?25hCollecting nvidia-cuda-runtime-cu11==11.7.99 Using cached nvidia_cuda_runtime_cu11-11.7.99-py3-none-manylinux1_x86_64.whl (849 kB) Collecting nvidia-cuda-nvrtc-cu11==11.7.99 Using cached nvidia_cuda_nvrtc_cu11-11.7.99-2-py3-none-manylinux1_x86_64.whl (21.0 MB) Collecting nvidia-cudnn-cu11==8.5.0.96 Using cached nvidia_cudnn_cu11-8.5.0.96-2-py3-none-manylinux1_x86_64.whl (557.1 MB) Requirement already satisfied: networkx in /home/mikolaj/.local/lib/python3.8/site-packages (from torch==2.0.0->torchtext) (2.8) Collecting nvidia-cublas-cu11==11.10.3.66 Using cached nvidia_cublas_cu11-11.10.3.66-py3-none-manylinux1_x86_64.whl (317.1 MB) Collecting nvidia-cusolver-cu11==11.4.0.1 Downloading nvidia_cusolver_cu11-11.4.0.1-2-py3-none-manylinux1_x86_64.whl (102.6 MB) [2K [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m102.6/102.6 MB[0m [31m5.6 MB/s[0m eta [36m0:00:00[0m00:01[0m00:01[0m [?25hRequirement already satisfied: typing-extensions in /home/mikolaj/.local/lib/python3.8/site-packages (from torch==2.0.0->torchtext) (4.2.0) Collecting nvidia-cusparse-cu11==11.7.4.91 Downloading nvidia_cusparse_cu11-11.7.4.91-py3-none-manylinux1_x86_64.whl (173.2 MB) [2K [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m173.2/173.2 MB[0m [31m6.6 MB/s[0m eta [36m0:00:00[0m00:01[0m00:01[0m [?25hCollecting nvidia-cuda-cupti-cu11==11.7.101 Downloading nvidia_cuda_cupti_cu11-11.7.101-py3-none-manylinux1_x86_64.whl (11.8 MB) [2K [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m11.8/11.8 MB[0m [31m7.9 MB/s[0m eta [36m0:00:00[0m00:01[0m00:01[0mm [?25hCollecting nvidia-curand-cu11==10.2.10.91 Downloading nvidia_curand_cu11-10.2.10.91-py3-none-manylinux1_x86_64.whl (54.6 MB) [2K [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m54.6/54.6 MB[0m [31m10.5 MB/s[0m eta [36m0:00:00[0m00:01[0m00:01[0m [?25hRequirement already satisfied: jinja2 in /home/mikolaj/.local/lib/python3.8/site-packages (from torch==2.0.0->torchtext) (3.0.3) Collecting nvidia-cufft-cu11==10.9.0.58 Downloading nvidia_cufft_cu11-10.9.0.58-py3-none-manylinux1_x86_64.whl (168.4 MB) [2K [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m168.4/168.4 MB[0m [31m3.8 MB/s[0m eta [36m0:00:00[0m00:01[0m00:01[0m [?25hCollecting sympy Downloading sympy-1.11.1-py3-none-any.whl (6.5 MB) [2K [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m6.5/6.5 MB[0m [31m1.8 MB/s[0m eta [36m0:00:00[0m00:01[0m00:01[0m [?25hCollecting triton==2.0.0 Downloading triton-2.0.0-1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (63.2 MB) [2K [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m63.2/63.2 MB[0m [31m2.4 MB/s[0m eta [36m0:00:00[0m00:01[0m00:01[0m [?25hRequirement already satisfied: filelock in /home/mikolaj/.local/lib/python3.8/site-packages (from torch==2.0.0->torchtext) (3.6.0) Collecting nvidia-nvtx-cu11==11.7.91 Downloading nvidia_nvtx_cu11-11.7.91-py3-none-manylinux1_x86_64.whl (98 kB) [2K [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m98.6/98.6 kB[0m [31m3.1 MB/s[0m eta [36m0:00:00[0m [?25hRequirement already satisfied: urllib3>=1.25 in /home/mikolaj/.local/lib/python3.8/site-packages (from torchdata==0.6.0->torchtext) (1.26.9) Requirement already satisfied: setuptools in /home/mikolaj/.local/lib/python3.8/site-packages (from nvidia-cublas-cu11==11.10.3.66->torch==2.0.0->torchtext) (63.4.2) Requirement already satisfied: wheel in /home/mikolaj/.local/lib/python3.8/site-packages (from nvidia-cublas-cu11==11.10.3.66->torch==2.0.0->torchtext) (0.37.1) Collecting lit Downloading lit-16.0.1.tar.gz (137 kB) [2K [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m137.9/137.9 kB[0m [31m1.8 MB/s[0m eta [36m0:00:00[0ma [36m0:00:01[0m [?25h Preparing metadata (setup.py) ... [?25ldone [?25hCollecting cmake Using cached cmake-3.26.3-py2.py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (24.0 MB) Requirement already satisfied: charset-normalizer~=2.0.0 in /home/mikolaj/.local/lib/python3.8/site-packages (from requests->torchtext) (2.0.10) Requirement already satisfied: idna<4,>=2.5 in /home/mikolaj/.local/lib/python3.8/site-packages (from requests->torchtext) (2.10) Requirement already satisfied: certifi>=2017.4.17 in /home/mikolaj/.local/lib/python3.8/site-packages (from requests->torchtext) (2022.12.7) Requirement already satisfied: MarkupSafe>=2.0 in /home/mikolaj/.local/lib/python3.8/site-packages (from jinja2->torch==2.0.0->torchtext) (2.0.1) Collecting mpmath>=0.19 Downloading mpmath-1.3.0-py3-none-any.whl (536 kB) [2K [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m536.2/536.2 kB[0m [31m768.5 kB/s[0m eta [36m0:00:00[0ma [36m0:00:01[0m [?25hBuilding wheels for collected packages: lit Building wheel for lit (setup.py) ... [?25ldone [?25h Created wheel for lit: filename=lit-16.0.1-py3-none-any.whl size=88173 sha256=fca0dda7f2dc27a2885356559af2c2b6bc26994156ad1efae9f15f63d3866468 Stored in directory: /home/mikolaj/.cache/pip/wheels/12/14/ba/87be46a564f97692e6cd1f6d7a1deeb5bff2821d45a52e8d7a Successfully built lit Installing collected packages: mpmath, lit, cmake, sympy, nvidia-nvtx-cu11, nvidia-nccl-cu11, nvidia-cusparse-cu11, nvidia-curand-cu11, nvidia-cufft-cu11, nvidia-cuda-runtime-cu11, nvidia-cuda-nvrtc-cu11, nvidia-cuda-cupti-cu11, nvidia-cublas-cu11, nvidia-cusolver-cu11, nvidia-cudnn-cu11, triton, torch, torchdata, torchtext Attempting uninstall: torch Found existing installation: torch 1.10.0 Uninstalling torch-1.10.0: Successfully uninstalled torch-1.10.0 [31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts. laserembeddings 1.1.2 requires sacremoses==0.0.35, which is not installed. unbabel-comet 1.1.0 requires numpy>=1.20.0, but you have numpy 1.19.3 which is incompatible. unbabel-comet 1.1.0 requires scipy>=1.5.4, but you have scipy 1.4.1 which is incompatible. unbabel-comet 1.1.0 requires torch<=1.10.0,>=1.6.0, but you have torch 2.0.0 which is incompatible. torchvision 0.11.1 requires torch==1.10.0, but you have torch 2.0.0 which is incompatible. torchaudio 0.10.0 requires torch==1.10.0, but you have torch 2.0.0 which is incompatible. laserembeddings 1.1.2 requires torch<2.0.0,>=1.0.1.post2, but you have torch 2.0.0 which is incompatible.[0m[31m [0mSuccessfully installed cmake-3.26.3 lit-16.0.1 mpmath-1.3.0 nvidia-cublas-cu11-11.10.3.66 nvidia-cuda-cupti-cu11-11.7.101 nvidia-cuda-nvrtc-cu11-11.7.99 nvidia-cuda-runtime-cu11-11.7.99 nvidia-cudnn-cu11-8.5.0.96 nvidia-cufft-cu11-10.9.0.58 nvidia-curand-cu11-10.2.10.91 nvidia-cusolver-cu11-11.4.0.1 nvidia-cusparse-cu11-11.7.4.91 nvidia-nccl-cu11-2.14.3 nvidia-nvtx-cu11-11.7.91 sympy-1.11.1 torch-2.0.0 torchdata-0.6.0 torchtext-0.15.1 triton-2.0.0 [1m[[0m[34;49mnotice[0m[1;39;49m][0m[39;49m A new release of pip available: [0m[31;49m22.2.2[0m[39;49m -> [0m[32;49m23.0.1[0m [1m[[0m[34;49mnotice[0m[1;39;49m][0m[39;49m To update, run: [0m[32;49mpip install --upgrade pip[0m
vocab.lookup_tokens([0, 1, 2, 10, 12345])
['<unk>', '\\\\', 'the', '-\\\\', 'wno']
Definicja sieci
Naszą prostą sieć neuronową zaimplementujemy używając frameworku PyTorch.
def look_ahead_iterator(gen):
prev = None
for item in gen:
if prev is not None:
yield (prev, item)
prev = item
list(look_ahead_iterator([1,2,3,4,5, 'X', 6 ]))
[(1, 2), (2, 3), (3, 4), (4, 5), (5, 'X'), ('X', 6)]
import itertools
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']]
Teraz wyuczmy model. Wpierw tylko potasujmy nasz plik:
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('train/in.tsv.xz', vocab_size)
from torch.utils.data import DataLoader
next(iter(train_dataset))
[0;31m---------------------------------------------------------------------------[0m [0;31mTypeError[0m Traceback (most recent call last) [0;32m/tmp/ipykernel_12664/602008184.py[0m in [0;36m<module>[0;34m[0m [1;32m 1[0m [0;32mfrom[0m [0mtorch[0m[0;34m.[0m[0mutils[0m[0;34m.[0m[0mdata[0m [0;32mimport[0m [0mDataLoader[0m[0;34m[0m[0;34m[0m[0m [1;32m 2[0m [0;34m[0m[0m [0;32m----> 3[0;31m [0mnext[0m[0;34m([0m[0mnext[0m[0;34m([0m[0miter[0m[0;34m([0m[0mtrain_dataset[0m[0;34m)[0m[0;34m)[0m[0;34m)[0m[0;34m[0m[0;34m[0m[0m [0m [0;31mTypeError[0m: 'tuple' object is not an iterator
vocab.lookup_tokens([43, 0])
['<s>', '<unk>']
from torch.utils.data import DataLoader
next(iter(DataLoader(train_dataset, batch_size=5)))
[tensor([ 2, 5, 51, 3481, 231]), tensor([ 5, 51, 3481, 231, 4])]
device = 'cpu'
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')
/home/mikolaj/.local/lib/python3.8/site-packages/torch/nn/modules/container.py:217: 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.0877, grad_fn=<NllLossBackward0>)
/home/mikolaj/.local/lib/python3.8/site-packages/torch/autograd/__init__.py:200: UserWarning: CUDA initialization: The NVIDIA driver on your system is too old (found version 10020). Please update your GPU driver by downloading and installing a new version from the URL: http://www.nvidia.com/Download/index.aspx Alternatively, go to: https://pytorch.org to install a PyTorch version that has been compiled with your version of the CUDA driver. (Triggered internally at ../c10/cuda/CUDAFunctions.cpp:109.) Variable._execution_engine.run_backward( # Calls into the C++ engine to run the backward pass
100 tensor(8.4388, grad_fn=<NllLossBackward0>) 200 tensor(7.7335, grad_fn=<NllLossBackward0>) 300 tensor(7.1300, grad_fn=<NllLossBackward0>) 400 tensor(6.7325, grad_fn=<NllLossBackward0>) 500 tensor(6.4705, grad_fn=<NllLossBackward0>) 600 tensor(6.0460, grad_fn=<NllLossBackward0>) 700 tensor(5.8104, grad_fn=<NllLossBackward0>) 800 tensor(5.8110, grad_fn=<NllLossBackward0>) 900 tensor(5.7169, grad_fn=<NllLossBackward0>) 1000 tensor(5.7580, grad_fn=<NllLossBackward0>) 1100 tensor(5.6787, grad_fn=<NllLossBackward0>) 1200 tensor(5.4501, grad_fn=<NllLossBackward0>)
[0;31m---------------------------------------------------------------------------[0m [0;31mKeyboardInterrupt[0m Traceback (most recent call last) [0;32m/tmp/ipykernel_12664/1293343661.py[0m in [0;36m<module>[0;34m[0m [1;32m 11[0m [0my[0m [0;34m=[0m [0my[0m[0;34m.[0m[0mto[0m[0;34m([0m[0mdevice[0m[0;34m)[0m[0;34m[0m[0;34m[0m[0m [1;32m 12[0m [0moptimizer[0m[0;34m.[0m[0mzero_grad[0m[0;34m([0m[0;34m)[0m[0;34m[0m[0;34m[0m[0m [0;32m---> 13[0;31m [0mypredicted[0m [0;34m=[0m [0mmodel[0m[0;34m([0m[0mx[0m[0;34m)[0m[0;34m[0m[0;34m[0m[0m [0m[1;32m 14[0m [0mloss[0m [0;34m=[0m [0mcriterion[0m[0;34m([0m[0mtorch[0m[0;34m.[0m[0mlog[0m[0;34m([0m[0mypredicted[0m[0;34m)[0m[0;34m,[0m [0my[0m[0;34m)[0m[0;34m[0m[0;34m[0m[0m [1;32m 15[0m [0;32mif[0m [0mstep[0m [0;34m%[0m [0;36m100[0m [0;34m==[0m [0;36m0[0m[0;34m:[0m[0;34m[0m[0;34m[0m[0m [0;32m~/.local/lib/python3.8/site-packages/torch/nn/modules/module.py[0m in [0;36m_call_impl[0;34m(self, *args, **kwargs)[0m [1;32m 1499[0m [0;32mor[0m [0m_global_backward_pre_hooks[0m [0;32mor[0m [0m_global_backward_hooks[0m[0;34m[0m[0;34m[0m[0m [1;32m 1500[0m or _global_forward_hooks or _global_forward_pre_hooks): [0;32m-> 1501[0;31m [0;32mreturn[0m [0mforward_call[0m[0;34m([0m[0;34m*[0m[0margs[0m[0;34m,[0m [0;34m**[0m[0mkwargs[0m[0;34m)[0m[0;34m[0m[0;34m[0m[0m [0m[1;32m 1502[0m [0;31m# Do not call functions when jit is used[0m[0;34m[0m[0;34m[0m[0;34m[0m[0m [1;32m 1503[0m [0mfull_backward_hooks[0m[0;34m,[0m [0mnon_full_backward_hooks[0m [0;34m=[0m [0;34m[[0m[0;34m][0m[0;34m,[0m [0;34m[[0m[0;34m][0m[0;34m[0m[0;34m[0m[0m [0;32m/tmp/ipykernel_12664/517511851.py[0m in [0;36mforward[0;34m(self, x)[0m [1;32m 14[0m [0;34m[0m[0m [1;32m 15[0m [0;32mdef[0m [0mforward[0m[0;34m([0m[0mself[0m[0;34m,[0m [0mx[0m[0;34m)[0m[0;34m:[0m[0;34m[0m[0;34m[0m[0m [0;32m---> 16[0;31m [0;32mreturn[0m [0mself[0m[0;34m.[0m[0mmodel[0m[0;34m([0m[0mx[0m[0;34m)[0m[0;34m[0m[0;34m[0m[0m [0m[1;32m 17[0m [0;34m[0m[0m [1;32m 18[0m [0mmodel[0m [0;34m=[0m [0mSimpleBigramNeuralLanguageModel[0m[0;34m([0m[0mvocab_size[0m[0;34m,[0m [0membed_size[0m[0;34m)[0m[0;34m[0m[0;34m[0m[0m [0;32m~/.local/lib/python3.8/site-packages/torch/nn/modules/module.py[0m in [0;36m_call_impl[0;34m(self, *args, **kwargs)[0m [1;32m 1499[0m [0;32mor[0m [0m_global_backward_pre_hooks[0m [0;32mor[0m [0m_global_backward_hooks[0m[0;34m[0m[0;34m[0m[0m [1;32m 1500[0m or _global_forward_hooks or _global_forward_pre_hooks): [0;32m-> 1501[0;31m [0;32mreturn[0m [0mforward_call[0m[0;34m([0m[0;34m*[0m[0margs[0m[0;34m,[0m [0;34m**[0m[0mkwargs[0m[0;34m)[0m[0;34m[0m[0;34m[0m[0m [0m[1;32m 1502[0m [0;31m# Do not call functions when jit is used[0m[0;34m[0m[0;34m[0m[0;34m[0m[0m [1;32m 1503[0m [0mfull_backward_hooks[0m[0;34m,[0m [0mnon_full_backward_hooks[0m [0;34m=[0m [0;34m[[0m[0;34m][0m[0;34m,[0m [0;34m[[0m[0;34m][0m[0;34m[0m[0;34m[0m[0m [0;32m~/.local/lib/python3.8/site-packages/torch/nn/modules/container.py[0m in [0;36mforward[0;34m(self, input)[0m [1;32m 215[0m [0;32mdef[0m [0mforward[0m[0;34m([0m[0mself[0m[0;34m,[0m [0minput[0m[0;34m)[0m[0;34m:[0m[0;34m[0m[0;34m[0m[0m [1;32m 216[0m [0;32mfor[0m [0mmodule[0m [0;32min[0m [0mself[0m[0;34m:[0m[0;34m[0m[0;34m[0m[0m [0;32m--> 217[0;31m [0minput[0m [0;34m=[0m [0mmodule[0m[0;34m([0m[0minput[0m[0;34m)[0m[0;34m[0m[0;34m[0m[0m [0m[1;32m 218[0m [0;32mreturn[0m [0minput[0m[0;34m[0m[0;34m[0m[0m [1;32m 219[0m [0;34m[0m[0m [0;32m~/.local/lib/python3.8/site-packages/torch/nn/modules/module.py[0m in [0;36m_call_impl[0;34m(self, *args, **kwargs)[0m [1;32m 1499[0m [0;32mor[0m [0m_global_backward_pre_hooks[0m [0;32mor[0m [0m_global_backward_hooks[0m[0;34m[0m[0;34m[0m[0m [1;32m 1500[0m or _global_forward_hooks or _global_forward_pre_hooks): [0;32m-> 1501[0;31m [0;32mreturn[0m [0mforward_call[0m[0;34m([0m[0;34m*[0m[0margs[0m[0;34m,[0m [0;34m**[0m[0mkwargs[0m[0;34m)[0m[0;34m[0m[0;34m[0m[0m [0m[1;32m 1502[0m [0;31m# Do not call functions when jit is used[0m[0;34m[0m[0;34m[0m[0;34m[0m[0m [1;32m 1503[0m [0mfull_backward_hooks[0m[0;34m,[0m [0mnon_full_backward_hooks[0m [0;34m=[0m [0;34m[[0m[0;34m][0m[0;34m,[0m [0;34m[[0m[0;34m][0m[0;34m[0m[0;34m[0m[0m [0;32m~/.local/lib/python3.8/site-packages/torch/nn/modules/activation.py[0m in [0;36mforward[0;34m(self, input)[0m [1;32m 1457[0m [0;34m[0m[0m [1;32m 1458[0m [0;32mdef[0m [0mforward[0m[0;34m([0m[0mself[0m[0;34m,[0m [0minput[0m[0;34m:[0m [0mTensor[0m[0;34m)[0m [0;34m->[0m [0mTensor[0m[0;34m:[0m[0;34m[0m[0;34m[0m[0m [0;32m-> 1459[0;31m [0;32mreturn[0m [0mF[0m[0;34m.[0m[0msoftmax[0m[0;34m([0m[0minput[0m[0;34m,[0m [0mself[0m[0;34m.[0m[0mdim[0m[0;34m,[0m [0m_stacklevel[0m[0;34m=[0m[0;36m5[0m[0;34m)[0m[0;34m[0m[0;34m[0m[0m [0m[1;32m 1460[0m [0;34m[0m[0m [1;32m 1461[0m [0;32mdef[0m [0mextra_repr[0m[0;34m([0m[0mself[0m[0;34m)[0m [0;34m->[0m [0mstr[0m[0;34m:[0m[0;34m[0m[0;34m[0m[0m [0;32m~/.local/lib/python3.8/site-packages/torch/nn/functional.py[0m in [0;36msoftmax[0;34m(input, dim, _stacklevel, dtype)[0m [1;32m 1841[0m [0mdim[0m [0;34m=[0m [0m_get_softmax_dim[0m[0;34m([0m[0;34m"softmax"[0m[0;34m,[0m [0minput[0m[0;34m.[0m[0mdim[0m[0;34m([0m[0;34m)[0m[0;34m,[0m [0m_stacklevel[0m[0;34m)[0m[0;34m[0m[0;34m[0m[0m [1;32m 1842[0m [0;32mif[0m [0mdtype[0m [0;32mis[0m [0;32mNone[0m[0;34m:[0m[0;34m[0m[0;34m[0m[0m [0;32m-> 1843[0;31m [0mret[0m [0;34m=[0m [0minput[0m[0;34m.[0m[0msoftmax[0m[0;34m([0m[0mdim[0m[0;34m)[0m[0;34m[0m[0;34m[0m[0m [0m[1;32m 1844[0m [0;32melse[0m[0;34m:[0m[0;34m[0m[0;34m[0m[0m [1;32m 1845[0m [0mret[0m [0;34m=[0m [0minput[0m[0;34m.[0m[0msoftmax[0m[0;34m([0m[0mdim[0m[0;34m,[0m [0mdtype[0m[0;34m=[0m[0mdtype[0m[0;34m)[0m[0;34m[0m[0;34m[0m[0m [0;31mKeyboardInterrupt[0m:
torch.save(model.state_dict(), 'model1.bin')
Policzmy najbardziej prawdopodobne kontynuacje dla zadanego słowa:
device = 'cpu'
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))
[(',', 3, 0.12514805793762207), ('\\\\', 1, 0.07237359136343002), ('<unk>', 0, 0.06839419901371002), ('.', 4, 0.06109621003270149), ('of', 5, 0.04557998105883598), ('and', 6, 0.03565318509936333), ('the', 2, 0.029342489317059517), ('to', 7, 0.02185475267469883), ('-\\\\', 10, 0.018097609281539917), ('in', 9, 0.016023961827158928)]
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.12514805793762207), ('\\\\', 1, 0.07237359136343002), ('<unk>', 0, 0.06839419901371002), ('.', 4, 0.06109621003270149), ('of', 5, 0.04557998105883598), ('and', 6, 0.03565318509936333), ('the', 2, 0.029342489317059517), ('to', 7, 0.02185475267469883), ('-\\\\', 10, 0.018097609281539917), ('in', 9, 0.016023961827158928)]
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))
[('<unk>', 0, 1.0000001192092896), ('nb', 1958, 0.4407886266708374), ('refrain', 14092, 0.4395471513271332), ('cat', 3391, 0.4154242277145386), ('enjoying', 7521, 0.3915165066719055), ('active', 1383, 0.38935279846191406), ('stewart', 4816, 0.3806381821632385), ('omit', 15600, 0.380504310131073), ('2041095573313', 11912, 0.37909239530563354), ('shut', 3863, 0.3778260052204132)]
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: