Lineární regrese v PyTorch¶
Cílem tohoto cvičení je napsat výpočet lineární regrese pomocí modulu nn.Linear() v PyTorch. Model chceme parametrizovat, tedy minimalizovat chybu modelu (loss) mezi daty a predikcí modelu.
Tento notebook je zároveň template proces pro další výpočty v PyTorch.
import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
Data¶
X = torch.linspace(1,50,50).reshape(-1,1)
X.shape
torch.Size([50, 1])
torch.manual_seed(59)
noise = torch.randint(-8,9,(50,1),dtype=torch.float)
print(noise.sum())
tensor(-42.)
y = 2 * X + 1 + noise
print(y.shape)
torch.Size([50, 1])
plt.scatter(X.numpy(), y.numpy())
plt.ylabel('y')
plt.xlabel('x');
Jednoduchý lineární model¶
PyTorch nn.Linear() model inicializuje váhy a odskok (bias) náhodnými čísly.
model = nn.Linear(in_features=1, out_features=1)
print(model.weight)
print(model.bias)
Parameter containing: tensor([[-0.5782]], requires_grad=True) Parameter containing: tensor([0.9358], requires_grad=True)
Třída modelu¶
PyTorch nám umožňuje definovat modely jako třídy objektů, které mohou uchovávat více vrstev modelu. V následujících částech nastavíme několik vrstev neuronové sítě a určíme, jak má každá vrstva provést svůj předávací průchod do další vrstvy. Prozatím však potřebujeme pouze jednu Liner() vrstvu.
def __init__()
inicializuje model pomocí vstupů a výstupů. Zde definujeme součásti modelu.
def forward()
je dopředný průchod modelu na základě složek z inicializace.
class Model(nn.Module):
def __init__(self, in_features, out_features):
super().__init__()
self.linear = nn.Linear(in_features, out_features)
def forward(self, x):
y_pred = self.linear(x)
return y_pred
model = Model(1,1)
model
Model( (linear): Linear(in_features=1, out_features=1, bias=True) )
Při instanci Modelu je třeba předat velikost (rozměry) vstupních a výstupních prvků. Pro naše účely použijeme (1,1).
torch.manual_seed(59)
model = Model(1, 1)
print(model)
print('Weight:', model.linear.weight.item())
print('Bias: ', model.linear.bias.item())
Model( (linear): Linear(in_features=1, out_features=1, bias=True) ) Weight: 0.10597813129425049 Bias: 0.9637961387634277
type(model.linear.weight.item())
float
# inicializované parametry modelu
for name, param in model.named_parameters():
print(name, '\t', param.item())
linear.weight 0.10597813129425049 linear.bias 0.9637961387634277
# dopředný chod
x = torch.tensor([2.0])
print(model.forward(x).item()) # equivalent to print(model(x))
1.1757524013519287
což souhlasí ... $f(x) = (0.1060)(2.0)+(0.9638) = 1.1758$
Garf inicializovaného modelu¶
Netrénovaný model můžeme porovnat s naším souborem dat, abychom si udělali představu o výchozím bodu.
x1 = np.array([X.min(),X.max()])
print(x1)
[ 1. 50.]
w1,b1 = model.linear.weight.item(), model.linear.bias.item()
print(f'Initial weight: {w1:.8f}, Initial bias: {b1:.8f}')
print()
y1 = x1*w1 + b1
print(y1)
Initial weight: 0.10597813, Initial bias: 0.96379614 [1.0697743 6.2627025]
plt.scatter(X.numpy(), y.numpy())
plt.plot(x1,y1,'r')
plt.title('Initial Model')
plt.ylabel('y')
plt.xlabel('x');
Ztrátová funkce (loss function)¶
Mohli bychom si napsat vlastní funkci pro použití střední kvadratické chyby (MSE), která je následující
$\begin{split}MSE &= \frac {1} {n} \sum_{i=1}^n {(y_i - \hat y_i)}^2 \\
&= \frac {1} {n} \sum_{i=1}^n {(y_i - (wx_i + b))}^2\end{split}$
PyTorch built-in: nn.MSELoss()
Podle konvence se používá název proměnné „criterion“, ale klidně použijte něco jako „linear_loss_func“, pokud je to jasnější.
criterion = nn.MSELoss()
criterion
MSELoss()
Optimalizace¶
Zde použijeme Stochastic Gradient Descent (SGD) s použitou úrovní učení (lr) 0,001. Připomeňme, že míra/rychlost učení říká optimalizátoru, jak moc má upravit jednotlivé parametry v dalším kole výpočtů. Při příliš velkém kroku se vystavujeme riziku překročení minima, což způsobí divergenci algoritmu. Příliš malý a konvergence bude trvat dlouho.
V případě složitějších (vícerozměrných) dat můžete také zvážit předání nepovinných argumentů momentum a váha_rozpadu. Momentum umožňuje algoritmu „přejet“ malé nerovnosti, aby se vyhnul lokálním minimům, která mohou způsobit příliš brzkou konvergenci. Rozpad váhy (nazývaný také L2 penalizace) se vztahuje na zkreslení.
Další informace naleznete v torch.optim.
model.named_parameters()
<generator object Module.named_parameters at 0x13ce26f90>
optimizer = torch.optim.SGD(model.parameters(), lr = 0.001)
optimizer
SGD ( Parameter Group 0 dampening: 0 differentiable: False foreach: None lr: 0.001 maximize: False momentum: 0 nesterov: False weight_decay: 0 )
Trénování modelu¶
epocha je jeden průchod celým souborem dat. Chceme zvolit dostatečně velký počet epoch, abychom dosáhli roviny blízké našim známým parametrům $\mathrm {weight} = 2,\; \mathrm {bias} = 1$.
- Nastavte přiměřeně velký počet průchodů
epochy = 50 - Vytvořte seznam pro ukládání hodnot ztrát. To nám umožní následně zobrazit náš postup.
ztráty = []
for i in range(epochs): - Přeskočte „i“ tak, aby vytištěná zpráva začínala na 1
i+=1 - Vytvořte sadu předpovědí tak, že proženete „X“ přes aktuální parametry modelu
y_pred = model.forward(X) - Vypočítejte ztrátu
loss = criterion(y_pred, y) - Přidejte hodnotu ztráty do našeho sledovacího seznamu
losses.append(loss) - Vypište aktuální řádek výsledků
print(f'epoch: {i:2} loss: {loss.item():10.8f}') - Gradienty se kumulují s každým zpětným krokem. Abychom zabránili hromadění, musíme pro každou novou epochu uložený gradient vynulovat.
optimizer.zero_grad() - Nyní můžeme backprop
loss.backward()
. - Nakonec můžeme aktualizovat hyperparametry našeho modelu
optimizer.step()
epochs = 30
losses = []
for i in range(epochs):
i+=1
y_pred = model.forward(X)
loss = criterion(y_pred, y)
losses.append(loss)
print(f'epoch: {i:2} loss: {loss.item():10.8f} weight: {model.linear.weight.item():10.8f} bias: {model.linear.bias.item():10.8f}')
optimizer.zero_grad()
loss.backward()
optimizer.step()
epoch: 1 loss: 3047.80468750 weight: 0.10597813 bias: 0.96379614 epoch: 2 loss: 1584.72033691 weight: 3.32878017 bias: 1.05878365 epoch: 3 loss: 829.38250732 weight: 1.01318645 bias: 0.98921829 epoch: 4 loss: 439.42785645 weight: 2.67701507 bias: 1.03788733 epoch: 5 loss: 238.10810852 weight: 1.48156786 bias: 1.00160384 epoch: 6 loss: 134.17364502 weight: 2.34055424 bias: 1.02636063 epoch: 7 loss: 80.51562500 weight: 1.72339821 bias: 1.00725961 epoch: 8 loss: 52.81362152 weight: 2.16687322 bias: 1.01967180 epoch: 9 loss: 38.51177597 weight: 1.84826875 bias: 1.00944197 epoch: 10 loss: 31.12798309 weight: 2.07722974 bias: 1.01548135 epoch: 11 loss: 27.31571007 weight: 1.91275680 bias: 1.00983167 epoch: 12 loss: 25.34730721 weight: 2.03097200 bias: 1.01258135 epoch: 13 loss: 24.33080292 weight: 1.94607139 bias: 1.00929666 epoch: 14 loss: 23.80573273 weight: 2.00711250 bias: 1.01034844 epoch: 15 loss: 23.53438187 weight: 1.96329260 bias: 1.00828505 epoch: 16 loss: 23.39401627 weight: 1.99481666 bias: 1.00846052 epoch: 17 loss: 23.32127190 weight: 1.97220492 bias: 1.00702798 epoch: 18 loss: 23.28343201 weight: 1.98849070 bias: 1.00675154 epoch: 19 loss: 23.26362610 weight: 1.97682786 bias: 1.00564504 epoch: 20 loss: 23.25312042 weight: 1.98524654 bias: 1.00513554 epoch: 21 loss: 23.24741364 weight: 1.97923636 bias: 1.00419772 epoch: 22 loss: 23.24419403 weight: 1.98359358 bias: 1.00356829 epoch: 23 loss: 23.24225426 weight: 1.98050141 bias: 1.00271785 epoch: 24 loss: 23.24097443 weight: 1.98276198 bias: 1.00202680 epoch: 25 loss: 23.24003410 weight: 1.98117626 bias: 1.00122190 epoch: 26 loss: 23.23926926 weight: 1.98235440 bias: 1.00049949 epoch: 27 loss: 23.23860550 weight: 1.98154652 bias: 0.99971843 epoch: 28 loss: 23.23798561 weight: 1.98216558 bias: 0.99898010 epoch: 29 loss: 23.23738098 weight: 1.98175931 bias: 0.99821168 epoch: 30 loss: 23.23679924 weight: 1.98208964 bias: 0.99746555
Graf vývoje ztráty při učení¶
# staré
losses_np = [l.item() for l in losses]
plt.plot(range(epochs), losses_np)
plt.ylabel('Loss')
plt.xlabel('epoch')
Výsledek¶
Nyní z nového modelu odvodíme y1 a vykreslíme poslední nejlépe odpovídající přímku.
w1,b1 = model.linear.weight.item(), model.linear.bias.item()
print(f'Current weight: {w1:.8f}, Current bias: {b1:.8f}')
y1 = x1*w1 + b1
print(x1)
print(y1)
Current weight: 1.98189092, Current bias: 0.99670404 [ 1. 50.] [ 2.978595 100.09125 ]
plt.scatter(X.numpy(), y.numpy())
plt.plot(x1,y1,'r')
plt.title('Current Model')
plt.ylabel('y')
plt.xlabel('x');