org
This commit is contained in:
parent
4dd0ace030
commit
65e28bbbb9
643
wyk/09_neurozoo.org
Normal file
643
wyk/09_neurozoo.org
Normal file
@ -0,0 +1,643 @@
|
||||
|
||||
* Neurozoo
|
||||
** Funkcja sigmoidalna
|
||||
|
||||
Funkcja sigmoidalna zamienia dowolną wartość („sygnał”) w wartość z przedziału $(0,1)$, czyli wartość, która może być interperetowana jako prawdopodobieństwo.
|
||||
|
||||
$$\sigma(x) = \frac{1}{1 + e^{-x}}$$
|
||||
|
||||
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
|
||||
import torch
|
||||
|
||||
def sigmoid(x):
|
||||
return 1 / (1 + torch.exp(-x))
|
||||
|
||||
sigmoid(torch.tensor(0.6))
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
:results:
|
||||
# Out[1]:
|
||||
: tensor(0.6457)
|
||||
:end:
|
||||
|
||||
#+BEGIN_SRC ipython :session mysession :results file
|
||||
%matplotlib inline
|
||||
import matplotlib.pyplot as plt
|
||||
import torch
|
||||
|
||||
x = torch.linspace(-5,5,100)
|
||||
plt.xlabel("x")
|
||||
plt.ylabel("y")
|
||||
plt.plot(x, sigmoid(x))
|
||||
fname = 'sigmoid.png'
|
||||
plt.savefig(fname)
|
||||
fname
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
[[file:# Out[32]:
|
||||
: 'sigmoid.png'
|
||||
[[file:./obipy-resources/Tb0Of9.png]]]]
|
||||
|
||||
*** PyTorch
|
||||
|
||||
Funkcja ~torch.sigmoid~ po prostu stosuje sigmoidę do każdego elementu tensora (/element-wise/).
|
||||
|
||||
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
|
||||
import torch
|
||||
|
||||
torch.sigmoid(torch.tensor([0.6, 1.0, -5.0]))
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
:results:
|
||||
# Out[38]:
|
||||
: tensor([0.6457, 0.7311, 0.0067])
|
||||
:end:
|
||||
|
||||
Istnieje również ~torch.nn.Sigmoid~, które może być używane jako warstwa.
|
||||
|
||||
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
|
||||
import torch.nn as nn
|
||||
|
||||
s = nn.Sigmoid()
|
||||
s(torch.tensor([0.0, -0.2, 0.4]))
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
:results:
|
||||
# Out[49]:
|
||||
: tensor([0.5000, 0.4502, 0.5987])
|
||||
:end:
|
||||
|
||||
**** Implementacja w Pytorchu
|
||||
|
||||
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
|
||||
import torch.nn as nn
|
||||
import torch
|
||||
|
||||
class MySigmoid(nn.Module):
|
||||
def __init__(self):
|
||||
super(MySigmoid, self).__init__()
|
||||
|
||||
def forward(self, x):
|
||||
return 1 / (1 + torch.exp(-x))
|
||||
|
||||
s = MySigmoid()
|
||||
s(torch.tensor([0.0, 0.5, 0.3]))
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
:results:
|
||||
# Out[48]:
|
||||
: tensor([0.5000, 0.6225, 0.5744])
|
||||
:end:
|
||||
|
||||
*** Wagi
|
||||
|
||||
Funkcja sigmoidalna nie ma żadnych wyuczalnych wag.
|
||||
|
||||
**** *Pytanie*: Czy można rozszerzyć funkcję sigmoidalną o jakieś wyuczalne wagi?
|
||||
|
||||
** Regresja liniowa
|
||||
|
||||
** Softmax
|
||||
|
||||
W klasyfikacji wieloklasowej należy zwrócić musimy zwrócić rozkład
|
||||
prawdopodobieństwa po wszystkich klasach, w przeciwieństwie do
|
||||
klasyfikacji binarnej, gdzie wystarczy zwrócić jedną liczbę —
|
||||
prawdopodobieństwo pozytywnej klasy ($p$; prawdopodobieństwo drugiej
|
||||
klasy to po prostu $1-p$).
|
||||
|
||||
A zatem na potrzeby klasyfikacji wieloklasowej potrzeba wektorowego
|
||||
odpowiednika funkcji sigmoidalnej, to jest funkcji, która zamienia
|
||||
nieznormalizowany wektor $\vec{z} = [z_1,\dots,z_k]$ (pochodzący np. z
|
||||
poprzedzającej warstwy liniowej) na rozkład prawdopobieństwa.
|
||||
Potrzebujemy zatem funkcji $s: \mathcal{R}^k \rightarrow [0,1]^k$
|
||||
|
||||
spełniającej następujące warunki:
|
||||
|
||||
- $s(z_i) = s_i(z) \in [0,1]$
|
||||
- $\Sigma_i s(z_i) = 1$
|
||||
- $z_i > z_j \Rightarrow s(z_i) > s(z_j)$
|
||||
|
||||
Można by podać takie (*błędne*!) rozwiązanie:
|
||||
|
||||
$$s(z_i) = \frac{z_i}{\Sigma_{j=1}^k z_j}$$
|
||||
|
||||
To rozwiązanie zadziała błędnie dla liczb ujemnych, trzeba najpierw
|
||||
użyć funkcji monotonicznej, która przekształaca $\mathcal{R}$ na $\mathcal{R^+}$.
|
||||
Naturalna funkcja tego rodzaju to funkcja wykładnicza $\exp{x} = e^x$.
|
||||
Tym sposobem dochodzimy do funkcji softmax:
|
||||
|
||||
$$s(z_i) = \frac{e^{z_i}}{\Sigma_{j=1}^k e^{z_j}}$$
|
||||
|
||||
Mianownik ułamka w definicji funkcji softmax nazywamy czasami czynnikiem normalizacyjnym:
|
||||
$Z(\vec{z}) = \Sigma_{j=1}^k e^{z_j}$, wtedy:
|
||||
|
||||
$$s(z_i) = \frac{e^{z_i}}{Z(\vec{z})}$$
|
||||
|
||||
Definicja w PyTorchu:
|
||||
|
||||
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
|
||||
import torch
|
||||
|
||||
def softmax(z):
|
||||
z_plus = torch.exp(z)
|
||||
return z_plus / torch.sum(z_plus)
|
||||
|
||||
softmax(torch.tensor([3., -1., 0., 5.]))
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
:results:
|
||||
# Out[75]:
|
||||
: tensor([0.1182, 0.0022, 0.0059, 0.8737])
|
||||
:end:
|
||||
|
||||
*** Soft vs hard
|
||||
|
||||
Dlaczego /softmax/? Czasami używa się funkcji *hardmax*, która np.
|
||||
wektora $[3, -1, 0, 5]$ zwróciłaby $[0, 0, 0, 5]$ — to jest po prostu
|
||||
wektorowa wersja funkcji zwracającej maksimum. Istnieje też funkcja
|
||||
hard*arg*max, która zwraca wektor /one-hot/ — z jedną jedynką na
|
||||
pozycji dla największej wartości (zamiast podania największej
|
||||
wartości), np. wartość hardargmax dla $[3, -1, 0, 5]$ zwróciłaby $[0,
|
||||
0, 0, 1]$.
|
||||
|
||||
Zauważmy, że powszechnie przyjęta nazwa /softmax/ jest właściwie
|
||||
błędna, funkcja ta powinna nazywać się /softargmax/, jako że w
|
||||
„miękki” sposób identyfikuje największą wartość przez wartość zbliżoną
|
||||
do 1 (na pozostałych pozycjach wektora nie będzie 0).
|
||||
|
||||
**** *Pytanie*: Jak można zdefiniować funkcję /softmax/ w ścisłym tego słowa znaczeniu („miękki” odpowiednik hardmax, nie hardargmax)?
|
||||
|
||||
|
||||
|
||||
*** PyTorch
|
||||
|
||||
Funkcja ~torch.nn.functional.softmax~ normalizuje wartości dla całego tensora:
|
||||
|
||||
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
|
||||
import torch.nn as nn
|
||||
|
||||
nn.functional.softmax(torch.tensor([0.6, 1.0, -5.0]))
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
:results:
|
||||
# Out[5]:
|
||||
: tensor([0.4007, 0.5978, 0.0015])
|
||||
:end:
|
||||
|
||||
… zobaczmy, jak ta funkcja zachowuje się dla macierzy:
|
||||
|
||||
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
|
||||
import torch.nn as nn
|
||||
|
||||
nn.functional.softmax(torch.tensor([[0.6, 1.0], [-2.0, 3.5]]))
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
:results:
|
||||
# Out[6]:
|
||||
#+BEGIN_EXAMPLE
|
||||
tensor([[0.4013, 0.5987],
|
||||
[0.0041, 0.9959]])
|
||||
#+END_EXAMPLE
|
||||
:end:
|
||||
|
||||
Za pomocą (zalecanego zresztą) argumentu ~dim~ możemy określić wymiar, wzdłuż którego dokonujemy normalizacji:
|
||||
|
||||
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
|
||||
import torch.nn as nn
|
||||
|
||||
nn.functional.softmax(torch.tensor([[0.6, 1.0], [-2.0, 3.5]]), dim=0)
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
:results:
|
||||
# Out[8]:
|
||||
#+BEGIN_EXAMPLE
|
||||
tensor([[0.9309, 0.0759],
|
||||
[0.0691, 0.9241]])
|
||||
#+END_EXAMPLE
|
||||
:end:
|
||||
|
||||
Istnieje również ~torch.nn.Softmax~, które może być używane jako warstwa.
|
||||
|
||||
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
|
||||
import torch.nn as nn
|
||||
|
||||
s = nn.Softmax(dim=0)
|
||||
s(torch.tensor([0.0, -0.2, 0.4]))
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
:results:
|
||||
# Out[10]:
|
||||
: tensor([0.3021, 0.2473, 0.4506])
|
||||
:end:
|
||||
|
||||
**** Implementacja w Pytorchu
|
||||
|
||||
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
|
||||
import torch.nn as nn
|
||||
import torch
|
||||
|
||||
class MySoftmax(nn.Module):
|
||||
def __init__(self):
|
||||
super(MySoftmax, self).__init__()
|
||||
|
||||
def forward(self, x):
|
||||
ex = torch.exp(x)
|
||||
return ex / torch.sum(ex)
|
||||
|
||||
s = MySigmoid()
|
||||
s(torch.tensor([0.0, 0.5, 0.3]))
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
:results:
|
||||
# Out[48]:
|
||||
: tensor([0.5000, 0.6225, 0.5744])
|
||||
:end:
|
||||
|
||||
***** *Pytanie*: Tak naprawdę wyżej zdefiniowana klasa ~MySoftmax~ nie zachowuje się identycznie jak ~nn.Softmax~. Na czym polega różnica?
|
||||
|
||||
*** Przypadek szczególny
|
||||
|
||||
Sigmoida jest przypadkiem szczególnym funkcji softmax:
|
||||
|
||||
$$\sigma(x) = \frac{1}{1 + e^{-x}} = \frac{e^x}{e^x + 1} = \frac{e^x}{e^x + e^0} = s([x, 0])_1$$
|
||||
|
||||
Ogólniej: softmax na dwuelementowych wektorach daje przesuniętą sigmoidę (przy ustaleniu jednej z wartości).
|
||||
|
||||
#+BEGIN_SRC ipython :session mysession :results file
|
||||
%matplotlib inline
|
||||
import matplotlib.pyplot as plt
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
x = torch.linspace(-5,5,100)
|
||||
plt.xlabel("x")
|
||||
plt.ylabel("y")
|
||||
a = torch.Tensor(x.size()[0]).fill_(2.)
|
||||
m = torch.stack([x, a])
|
||||
plt.plot(x, nn.functional.softmax(m, dim=0)[0])
|
||||
fname = 'softmax3.png'
|
||||
plt.savefig(fname)
|
||||
fname
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
[[file:# Out[19]:
|
||||
: 'softmax3.png'
|
||||
[[file:./obipy-resources/gjBA7K.png]]]]
|
||||
|
||||
#+BEGIN_SRC ipython :session mysession :results file
|
||||
%matplotlib inline
|
||||
import matplotlib.pyplot as plt
|
||||
from mpl_toolkits import mplot3d
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
x = torch.linspace(-5,5,10)
|
||||
y = torch.linspace(-5,5,10)
|
||||
fig = plt.figure()
|
||||
ax = fig.add_subplot(111, projection='3d')
|
||||
plt.xlabel("x")
|
||||
plt.ylabel("y")
|
||||
X, Y = torch.meshgrid(x, y)
|
||||
m = torch.stack([X, Y])
|
||||
z = nn.functional.softmax(m, dim=0)
|
||||
ax.plot_wireframe(x, y, z[0])
|
||||
fname = 'softmax3d.png'
|
||||
plt.savefig(fname)
|
||||
fname
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
[[file:# Out[27]:
|
||||
: 'softmax3d.png'
|
||||
[[file:./obipy-resources/p96515.png]]]]
|
||||
|
||||
*** Wagi
|
||||
|
||||
Podobnie jak funkcja sigmoidalna, softmax nie ma żadnych wyuczalnych wag.
|
||||
|
||||
*** Zastosowania
|
||||
|
||||
Podstawowym zastosowaniem funkcji softmax jest klasyfikacja
|
||||
wieloklasowa, również w wypadku zadań przetwarzania sekwencji, które
|
||||
mogą być interpretowane jako klasyfikacja wieloklasowa:
|
||||
|
||||
- przewidywanie kolejnego słowa w modelowaniu języka (klasą jest słowo, zbiór klas to słownik)
|
||||
- przypisywanie etykiet (np. części mowy) słowom.
|
||||
|
||||
** LogSoftmax
|
||||
|
||||
Ze względów obliczeniowych często korzysta się z funkcji *LogSoftmax*
|
||||
która zwraca logarytmy pradopodobieństw (/logproby/).
|
||||
|
||||
$$log s(z_i) = log \frac{e^{z_i}}{\Sigma_{j=1}^k e^{z_j}}$$
|
||||
|
||||
*** PyTorch
|
||||
|
||||
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
|
||||
import torch.nn as nn
|
||||
|
||||
s = nn.LogSoftmax(dim=0)
|
||||
s(torch.tensor([0.0, -0.2, 0.4]))
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
:results:
|
||||
# Out[25]:
|
||||
: tensor([-1.1971, -1.3971, -0.7971])
|
||||
:end:
|
||||
|
||||
Niektóre funkcje kosztu (np. ~NLLLoss~) zaimplementowane w PyTorchu
|
||||
operują właśnie na logarytmach prawdopobieństw.
|
||||
|
||||
** Przykład: klasyfikacja wieloklasowa
|
||||
|
||||
Na przykładzie rozpoznawania dyscypliny sportu: git://gonito.net/sport-text-classification.git
|
||||
|
||||
Wczytujemy zbiór uczący:
|
||||
|
||||
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
|
||||
import gzip
|
||||
from pytorch_regression.analyzer import vectorize_text, vector_length
|
||||
|
||||
texts = []
|
||||
labels = []
|
||||
labels_dic = {}
|
||||
labels_revdic = {}
|
||||
c = 0
|
||||
|
||||
with gzip.open('sport-text-classification/train/train.tsv.gz', 'rt') as fh:
|
||||
for line in fh:
|
||||
line = line.rstrip('\n')
|
||||
line = line.replace('\\\t', ' ')
|
||||
label, text = line.split('\t')
|
||||
texts.append(text)
|
||||
if label not in labels_dic:
|
||||
labels_dic[label] =c
|
||||
labels_revdic[c] = label
|
||||
c += 1
|
||||
labels.append(labels_dic[label])
|
||||
nb_of_labels = len(labels_dic)
|
||||
labels_dic
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
:results:
|
||||
# Out[23]:
|
||||
#+BEGIN_EXAMPLE
|
||||
{'zimowe': 0,
|
||||
'moto': 1,
|
||||
'tenis': 2,
|
||||
'pilka-reczna': 3,
|
||||
'sporty-walki': 4,
|
||||
'koszykowka': 5,
|
||||
'siatkowka': 6,
|
||||
'pilka-nozna': 7}
|
||||
#+END_EXAMPLE
|
||||
:end:
|
||||
|
||||
Przygotowujemy model:
|
||||
|
||||
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
|
||||
import torch.nn as nn
|
||||
from torch import optim
|
||||
|
||||
model = nn.Sequential(
|
||||
nn.Linear(vector_length, nb_of_labels),
|
||||
nn.LogSoftmax()
|
||||
)
|
||||
|
||||
optimizer = optim.Adam(model.parameters())
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
:results:
|
||||
# Out[8]:
|
||||
:end:
|
||||
|
||||
Funkcja kosztu to log-loss.
|
||||
|
||||
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
loss_fn = torch.nn.NLLLoss()
|
||||
|
||||
expected_class_id = torch.tensor([2])
|
||||
loss_fn(torch.log(
|
||||
torch.tensor([[0.3, 0.5, 0.1, 0.0, 0.1]])),
|
||||
expected_class_id)
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
:results:
|
||||
# Out[9]:
|
||||
: tensor(2.3026)
|
||||
:end:
|
||||
|
||||
Pętla ucząca:
|
||||
|
||||
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
|
||||
iteration = 0
|
||||
step = 50
|
||||
closs = torch.tensor(0.0, dtype=torch.float, requires_grad=False)
|
||||
|
||||
for t, y_exp in zip(texts, labels):
|
||||
x = vectorize_text(t).float().unsqueeze(dim=0)
|
||||
|
||||
optimizer.zero_grad()
|
||||
|
||||
y_logprobs = model(x)
|
||||
|
||||
loss = loss_fn(y_logprobs, torch.tensor([y_exp]))
|
||||
|
||||
loss.backward()
|
||||
|
||||
with torch.no_grad():
|
||||
closs += loss
|
||||
|
||||
optimizer.step()
|
||||
|
||||
if iteration % 50 == 0:
|
||||
print((closs / step).item(), loss.item(), iteration, y_exp, torch.exp(y_logprobs), t)
|
||||
closs = torch.tensor(0.0, dtype=torch.float, requires_grad=False)
|
||||
iteration += 1
|
||||
|
||||
if iteration == 5000:
|
||||
break
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
:results:
|
||||
# Out[25]:
|
||||
:end:
|
||||
|
||||
Model jest tak prosty, że jego wagi są interpretowalne.
|
||||
|
||||
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
|
||||
with torch.no_grad():
|
||||
x = vectorize_text('NBA').float().unsqueeze(dim=0)
|
||||
y_prob = model(x)
|
||||
torch.exp(y_prob)
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
:results:
|
||||
# Out[26]:
|
||||
: tensor([[0.0070, 0.0075, 0.0059, 0.0061, 0.0093, 0.9509, 0.0062, 0.0071]])
|
||||
:end:
|
||||
|
||||
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
|
||||
with torch.no_grad():
|
||||
x = vectorize_text('NBA').float().unsqueeze(dim=0)
|
||||
ix = torch.argmax(x).item()
|
||||
model[0].weight[:,ix]
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
:results:
|
||||
# Out[32]:
|
||||
#+BEGIN_EXAMPLE
|
||||
tensor([-2.3693, -2.3421, -2.4205, -2.4353, -2.1499, 2.5163, -2.4351, -2.4546],
|
||||
grad_fn=<SelectBackward>)
|
||||
#+END_EXAMPLE
|
||||
:end:
|
||||
|
||||
Możemy nawet zaprezentować wykres przedstawiający rozmieszczenie słów względem dwóch osi odnoszących się do poszczególnych wybranych dyscyplin.
|
||||
|
||||
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
|
||||
%matplotlib inline
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
with torch.no_grad():
|
||||
words = ['piłka', 'klub', 'kort', 'boisko', 'samochód']
|
||||
words_ixs = [torch.argmax(vectorize_text(w).float().unsqueeze(dim=0)).item() for w in words]
|
||||
|
||||
x_label = labels_dic['pilka-nozna']
|
||||
y_label = labels_dic['tenis']
|
||||
|
||||
x = [model[0].weight[x_label, ix] for ix in words_ixs]
|
||||
y = [model[0].weight[y_label, ix] for ix in words_ixs]
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
ax.scatter(x, y)
|
||||
|
||||
for i, txt in enumerate(words):
|
||||
ax.annotate(txt, (x[i], y[i]))
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
:results:
|
||||
# Out[45]:
|
||||
[[file:./obipy-resources/5egYcv.png]]
|
||||
:end:
|
||||
|
||||
** Zadanie etykietowania sekwencji
|
||||
|
||||
Zadanie etykietowania sekwencji (/sequence labelling/) polega na przypisaniu poszczególnym wyrazom (tokenom) tekstu *etykiet* ze skończonego zbioru. Definiując formalnie:
|
||||
|
||||
- rozpatrujemy ciąg wejściowy tokenów $(t^1,\dots,t^K)$
|
||||
- dany jest skończony zbiór etykiet $L = \{l_1,\dots,l_{|L|}\}$, dla uproszczenia można założyć, że etykietami
|
||||
są po prostu kolejne liczby, tj. $L=\{0,\dots,|L|-1\}$
|
||||
- zadanie polega na wygenerowaniu sekwencji etykiet (o tej samej długości co ciąg wejściowy!) $(y^1,\dots,y^K)$,
|
||||
$y^k \in L$
|
||||
|
||||
Zadanie etykietowania można traktować jako przypadek szczególny klasyfikacji wieloklasowej, z tym, że klasyfikacji dokonujemy wielokrotnie — dla każdego tokenu (nie dla każdego tekstu).
|
||||
|
||||
Przykłady zastosowań:
|
||||
|
||||
- oznaczanie częściami mowy (/POS tagger/) — czasownik, przymiotnik, rzeczownik itd.
|
||||
- oznaczanie etykiet nazw w zadaniu NER (nazwisko, kwoty, adresy — najwięcej tokenów będzie miało etykietę pustą, zazwyczaj oznaczaną przez ~O~)
|
||||
|
||||
*** *Pytanie*: czy zadanie tłumaczenia maszynowego można potraktować jako problem etykietowania sekwencji?
|
||||
|
||||
*** Przykładowe wyzwanie NER CoNLL-2003
|
||||
|
||||
Zob. <https://gonito.net/challenge/en-ner-conll-2003>.
|
||||
|
||||
Przykładowy przykład uczący (~xzcat train.tsv.xz| head -n 1~):
|
||||
|
||||
O O B-MISC I-MISC O O O O O B-LOC O B-LOC O O O O O O O O O O O B-MISC I-MISC O O O O O B-PER I-PER O B-LOC O O O O O O B-PER I-PER O B-LOC O O O O O O B-PER I-PER I-PER O B-LOC O O O O O B-PER I-PER O O B-LOC O O O O O O B-PER I-PER O B-LOC O O O O O B-PER I-PER O O O O O B-PER I-PER O B-LOC O O O O O B-PER I-PER O B-LOC O B-LOC O O O O O O B-PER I-PER O O O O B-PER I-PER O B-LOC O O O O O O B-PER I-PER O B-LOC O O O O O B-PER I-PER O O O O O B-PER I-PER O B-LOC O O O O O B-PER I-PER O B-LOC O O O O O O B-PER I-PER O O O O O B-PER I-PER O B-LOC O O O O O B-PER I-PER O B-LOC O O O O O O B-PER I-PER O O O O B-PER I-PER I-PER O B-LOC O O O O O O B-PER I-PER O O O O B-PER I-PER O B-LOC O O O O O O B-PER I-PER O B-LOC O O O O O O B-PER I-PER O O O O B-PER I-PER O B-LOC O O O O O O B-PER I-PER O O O O B-PER I-PER O B-LOC O O O O O O B-PER I-PER O B-LOC O O O O O B-PER I-PER O B-LOC O B-LOC O O O O O B-PER I-PER O O O O O GOLF - BRITISH MASTERS THIRD ROUND SCORES . </S> NORTHAMPTON , England 1996-08-30 </S> Leading scores after </S> the third round of the British Masters on Friday : </S> 211 Robert Allenby ( Australia ) 69 71 71 </S> 212 Pedro Linhart ( Spain ) 72 73 67 </S> 216 Miguel Angel Martin ( Spain ) 75 70 71 , Costantino Rocca </S> ( Italy ) 71 73 72 </S> 217 Antoine Lebouc ( France ) 74 73 70 , Ian Woosnam 70 76 71 , </S> Francisco Cea ( Spain ) 70 71 76 , Gavin Levenson ( South </S> Africa ) 66 75 76 </S> 218 Stephen McAllister 73 76 69 , Joakim Haeggman ( Swe ) 71 77 </S> 70 , Jose Coceres ( Argentina ) 69 78 71 , Paul Eales 75 71 72 , </S> Klas Eriksson ( Sweden ) 71 75 72 , Mike Clayton ( Australia ) </S> 69 76 73 , Mark Roe 69 71 78 </S> 219 Eamonn Darcy ( Ireland ) 74 76 69 , Bob May ( U.S. ) 74 75 70 , </S> Paul Lawrie 72 75 72 , Miguel Angel Jimenez ( Spain ) 74 72 </S> 73 , Peter Mitchell 74 71 75 , Philip Walton ( Ireland ) 71 74 </S> 74 , Peter O'Malley ( Australia ) 71 73 75 </S> 220 Barry Lane 73 77 70 , Wayne Riley ( Australia ) 71 78 71 , </S> Martin Gates 71 77 72 , Bradley Hughes ( Australia ) 73 75 72 , </S> Peter Hedblom ( Sweden ) 70 75 75 , Retief Goosen ( South </S> Africa ) 71 74 75 , David Gilford 69 74 77 . </S>
|
||||
|
||||
W pierwszym polu oczekiwany wynik zapisany za pomocą notacji *BIO*.
|
||||
|
||||
Jako metrykę używamy F1 (z pominięciem tagu ~O~)
|
||||
|
||||
*** Metryka F1
|
||||
|
||||
*** Etykietowanie za pomocą klasyfikacji wieloklasowej
|
||||
|
||||
Można potraktować problem etykietowania dokładnie tak jak problem
|
||||
klasyfikacji wieloklasowej (jak w przykładzie klasyfikacji dyscyplin
|
||||
sportowych powyżej), tzn. rozkład prawdopodobieństwa możliwych etykiet
|
||||
uzyskujemy poprzez zastosowanie prostej warstwy liniowej i funkcji softmax:
|
||||
|
||||
$$p(l^k=i) = s(\vec{w}\vec{v}(t^k))_i = \frac{e^{\vec{w}\vec{v}(t^k)}}{Z},$$
|
||||
|
||||
gdzie $\vec{v}(t^k)$ to reprezentacja wektorowa tokenu $t^k$.
|
||||
Zauważmy, że tutaj (w przeciwieństwie do klasyfikacji całego tekstu)
|
||||
reprezentacja wektorowa jest bardzo uboga: wektor _one-hot_! Taki
|
||||
klasyfikator w ogóle nie będzie brał pod uwagę kontekstu, tylko sam
|
||||
wyraz, więc tak naprawdę zdegeneruje się to do zapamiętania częstości
|
||||
etykiet dla każdego słowa osobno.
|
||||
|
||||
**** Bogatsza reprezentacja słowa
|
||||
|
||||
Można spróbować uzyskać bogatszą reprezentację dla słowa biorąc pod uwagę na przykład:
|
||||
|
||||
- długość słowa
|
||||
- kształt słowa (/word shape/), np. czy pisany wielkimi literami, czy składa się z cyfr itp.
|
||||
- n-gramy znakowe wewnątrz słowa (np. słowo /Kowalski/ można zakodować jako sumę wektorów
|
||||
trigramów znakówych $\vec{v}(Kow) + \vec{v}(owa) + \vec{v}(wal) + \vec{v}(als) + \vec{v}(lsk) + + \vec{v}(ski)$
|
||||
|
||||
Cały czas nie rozpatrujemy jednak w tej metodzie kontekstu wyrazu.
|
||||
(/Renault/ w pewnym kontekście może być nazwą firmy, w innym —
|
||||
nazwiskiem).
|
||||
|
||||
**** Reprezentacja kontekstu
|
||||
|
||||
Za pomocą wektora można przedstawić nie pojedynczy token $t^k$, lecz
|
||||
cały kontekst, dla /okna/ o długości $c$ będzie to kontekst $t^{k-c},\dots,t^k,\dots,t^{k+c}$.
|
||||
Innymi słowy klasyfikujemy token na podstawie jego samego oraz jego kontekstu:
|
||||
|
||||
$$p(l^k=i) = \frac{e^{\vec{w}\vec{v}(t^{k-c},\dots,t^k,\dots,t^{k+c})}}{Z_k}.$$
|
||||
|
||||
Zauważmy, że w tej metodzie w ogóle nie rozpatrujemy sensowności
|
||||
sekwencji wyjściowej (etykiet), np. może być bardzo mało
|
||||
prawdopodobne, że bezpośrednio po nazwisku występuje data.
|
||||
|
||||
Napiszmy wzór określający prawdopodobieństwo całej sekwencji, nie
|
||||
tylko pojedynczego tokenu. Na razie będzie to po prostu iloczyn poszczególnych wartości.
|
||||
|
||||
$$p(l) = \prod_{k=1}^K \frac{e^{\vec{w}\vec{v}(t^{k-c},\dots,t^k,\dots,t^{k+c})}}{Z_k} = \frac{e^{\sum_{k=1}^K\vec{w}\vec{v}(t^{k-c},\dots,t^k,\dots,t^{k+c})}}{\prod_{k=1}^K Z_k}$$
|
||||
|
||||
** Warunkowe pola losowe
|
||||
|
||||
Warunkowe pola losowe (/Conditional Random Fields/, /CRF/) to klasa
|
||||
modeli, które pozwalają uwzględnić zależności między punktami danych
|
||||
(które można wyrazić jako graf). Najprostszym przykładem będzie prosty
|
||||
graf wyrażający „następowanie po” (czyli sekwencje). Do poprzedniego
|
||||
wzoru dodamy składnik $V_{i,j}$ (który można interpretować jako
|
||||
macierz) określający prawdopodobieństwo, że po etykiecie o numerze $i$ wystąpi etykieta o numerze $j$.
|
||||
|
||||
*** *Pytanie*: Czy macierz $V$ musi być symetryczna? Czy $V_{i,j} = V_{j,i}$? Czy jakieś specjalne wartości występują na przekątnej?
|
||||
|
||||
Macierz $V$ wraz z wektorem $\vec{w}$ będzie stanowiła wyuczalne wagi w naszym modelu.
|
||||
|
||||
Wartości $V_{i,j}$ nie stanowią bezpośrednio prawdopodobieństwa, mogą
|
||||
przyjmować dowolne wartości, które będę normalizowane podobnie jak to się dzieje w funkcji Softmax.
|
||||
|
||||
W takiej wersji warunkowych pól losowych otrzymamy następujący wzór na prawdopodobieństwo całej sekwencji.
|
||||
|
||||
$$p(l) = \frac{e^{\sum_{k=1}^K\vec{w}\vec{v}(t^{k-c},\dots,t^k,\dots,t^{k+c}) + \sum_{k=1}^{K-1} V_{l_k,l_{k+1}}}}{\prod_{k=1}^K Z_k}$$
|
Loading…
Reference in New Issue
Block a user