ML: Нейронні мережі в Torch - Довідник


Інтерфейси для шарів

Цей документ містить огляд основних типів шарів, похибок і оптимізаторів у бібліотеці PyTorch. Основи PyTorch описані тут, а основи програмування на ній нейронних мереж - тут.

Шари в PyTorch реалізовані у двох формах: як функції і як класи.

Перша - це звичайні функції, яким передаються параметри шару і вхідний тензор. Наприклад, лінійний шар множить вхідну матрицю форми (N, nX) на транспоновану матрицю ваг W форми (nY, nX) і опційно додає вектор зсувів bias. На виході виходить тензор (N, nY):

import  torch.nn.functional as F

N, nX, nY = 1, 2, 3                               # кількість прикладів, входів, виходів
X = torch.ones(N,  nX)                            # матриця прикладів
W = torch.ones(nY, nX)                            # матриця ваг 
B = torch.ones(nY)                                # вектор зміщень

Y = F.linear(X, W, B)                             # [[3., 3., 3.]]
Y = X.mm(W.t())+B                                 # те саме
Y = torch.addmm(B,X,W.t())                        # те саме
Шари як екземпляри класів, самі створюють параметри з початковими випадковими значеннями.
Нижче обов'язковими аргументами при створенні класу є число входів nX і число виходів nY шару (число нейронів у ньому == число нових ознак). Потім екземпляру fc класу Linear через оператор ( ) передається вхідна матриця X:
import torch.nn as nn

fc =  nn.Linear(nX, nY)
Y  = fc(X)                                        # tensor([[-0.1559, -0.2140, 0.1462]],
                                                  #         grad_fn=<Addmm>)

Клас шару у своїх параметрів оголошує властивість requires_grad=True. Це призводить до побудови обчислювального графа при прямому поширенні по мережі і до отримання градієнтів від параметрів при зворотному поширенні:

print(fc.bias)            # tensor([ 0.529, -0.103,  0.646], requires_grad=True)
print(fc.bias.is_leaf)    # True
print(fc.bias.grad)       # None

L = torch.sum(Y*Y)        # скалярна "функція помилки"
L.backward()              # запускаємо обчислення градієнтів
fc.bias.grad              # tensor([-0.3688,  0.6441, -1.6415])

Далі йде невеликий довідник найважливіших типів шарів, активаційних функцій і оптимізаторів. За докладнішою інформацією варто звернутися до документації.


Базові шари


Повнозв'язний шар

nn.Linear
… (in_features, out_features, bias=True)
[doc]

Здійснює лінійне перетворення $\mathbf{y} = \mathbf{x}\cdot \mathbf{W}^T + \mathbf{b}$. Параметри шару знаходяться в атрибутах weight і bias. Матриця ваг $\mathbf{W}$ зберігається в транспонованому вигляді (щоб при перемноженні матриць $\mathbf{X}\mathbf{W}^\top$ використовувати їхні рядки, а не рядок і стовпець). Якщо необхідно задати ваги руками, це робиться поза обчислювальним графом:

fc = nn.Linear(nX, nY)
with torch.no_grad():
    fc.weight.copy_(W)     # встановлюємо матрицю ваг
    fc.bias.  copy_(B)     # встановлюємо вектор зміщень
Щоб заморозити параметри (не змінювати при навчанні) достатньо вимкнути в них обчислення градієнта: fc.weight.requires_grad = False.

Реалізація лінійного шару в torch.nn.functional виглядає таким чином:

def linear(X, weight, bias=None):
    if X.dim() == 2 and bias is not None:       # fused op is marginally faster 
        ret = torch.addmm(bias, X, weight.t())  # bias + X @ weight.t()
    else:
        output = X.matmul(weight.t())
        if bias is not None: output += bias
        ret = output
    return ret

Нехай in_features=nX, out_features=nY і є зсув: bias=True. Вхідний тензор може мати довільне число індексів (три крапки) і шар так змінює його форму: (N,...,nX) -> (N,...,nY).
Число параметрів шару дорівнює nX*nY + nY.


Ембединг

nn.Embedding
… (num_embeddings, embedding_dim, padding_idx=None, max_norm=None, norm_type=2.0,
… scale_grad_by_freq=False, sparse=False, _weight=None)

Шар Embedding перетворює масив цілих чисел long на вході на матрицю (масив векторів). Докладніше див. Embedding слів. При створенні екземпляра шару обов'язкові два перших параметри: розмір словника і розмірність ембедингу. Нижче створюється шар з 5 словами і їхніми 2-вимірними (випадковими) векторами:

emb = nn.Embedding(5, 2)

X = torch.tensor([0,2,1])

print (emb(X))
print(emb.weight)
tensor([[-1.3062, -1.6261],
        [-0.3903,  0.0998],
        [ 0.2643,  1.6466]],  grad_fn=<Embedding>)
Parameter containing:
tensor([[-1.3062, -1.6261],
        [ 0.2643,  1.6466],
        [-0.3903,  0.0998],
        [-0.5463, -0.5028],
        [ 1.3299, -0.0580]],  requires_grad=True)
При встановленні параметра padding_idx=0, слово з нульовим індексом завжди матиме нульові компоненти. Подібний падінг використовується в реченнях змінної довжини. Нулями забиваються "неіснуючі слова" для вирівнювання довжин речень.

Параметр max_norm задає максимальну довжину векторів і якщо при навчанні вона перевищується, вектор перенормовується. Як норма за замовчуванням використовується сума квадратів компонент: norm_type = 2.

При параметрі scale_grad_by_freq = True градієнти при навчанні будуть зважуватися за оберненою частотою слів у батчі (посилювати зміну більш рідкісних слів). Але батч при цьому варто брати достатньо великий. Завантажити готові вектори можна, створивши шар таким чином:

weight = torch.FloatTensor([[1, 2.3, 3], 
                            [4, 5.1, 6]])
emb = nn.Embedding.from_pretrained(weight)    # Створити шар із готовими векторами
input = torch.LongTensor([1])                 # Get embeddings for index 1
emb(input)                                    # tensor([[ 4.0,  5.1,  6.0]])
Параметри функції from_pretrained:
from_pretrained(embeddings, freeze=True, padding_idx=None, 
                max_norm=None, norm_type=2.0, scale_grad_by_freq=False, sparse=False)

Конволюція

nn.Conv2d
… (in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True,
… padding_mode='zeros')

Згортковий шар Conv2D застосовується до "картинок" висотою rows, шириною cols і з in_channels "кольоровими" каналами. Насправді графічні терміни умовні і шар Conv2D може застосовуватися не тільки при обробці зображень. Форма вхідного тензора (N, in_channels, rows, cols). На виході буде тензор (N, out_channels, rows_out, cols_out) За вихідним тензором проходить out_channels фільтрів розміром kernel_size (число або tuple). Глибина фільтра визначається числом вхідних каналів in_channels.

Нижче на малюнку in_channels=2, out_channels=3 (три фільтри), розміри фільтрів kernel_size = 2 або, що те саме kernel_size = (2,2). Фільтри за замовчуванням ковзають по картинці з кроком один stride=(1,1), тому результуюча ширина і висота картинки на 1 менші:

Параметр padding (int or tuple) задає на скільки "пікселів" необхідно розширити картинку з обох сторін, перед проходженням по ній фільтрами. Розширена область заповнюється значеннями відповідно до параметра padding_mode: 'zeros', 'reflect', 'replicate', 'circular'. Параметр dilation = 1 задає відстань між пікселями, що потрапляють у фільтр.

Для прикладу створимо "картинку" 3x4, ліва половина якої біла (1), а права - чорна (0) і пройдемо по ній фільтром 2x2, що виділяє вертикальний край:

X = torch.zeros(1,1,3,4)
X[0,0,:,:2] = 1
print(X)

conv = nn.Conv2d(1,1,kernel_size=2,bias=False)
print( conv ) 

with torch.no_grad():
    conv.weight.copy_(torch.tensor([[-1.,1.], 
                                    [-1.,1.]]))

print( conv.weight )
print( conv.bias )
print( conv(X) )

tensor([[[[1., 1., 0., 0.],
          [1., 1., 0., 0.],
          [1., 1., 0., 0.]]]])
          
Conv2d(1,1,kernel_size=(2,2),stride=(1,1))


# задаємо ядро фільтра
tensor([[[[-1.,1.],
          [-1.,1.]]]],requires_grad=True)
None
tensor([[[[ 0., -2.,  0.],
          [ 0., -2.,  0.]]]], 
          grad_fn=<ThnnConv2D>)

RNN

nn.RNN
… (input_size, hidden_size, num_layers=1, nonlinearity='tanh', bias=True,
… batch_first=False, dropout=0, bidirectional=False)
[doc]

Базовий рекурентний шар за входом $\mathbf{x}^{(t)}$ і попереднім прихованим станом $\mathbf{h}^{(t-1)}$ обчислюється новий прихований стан $\mathbf{h}^{(t)}$ : $$ \mathbf{h}^{(t)} = \tanh(\mathbf{x}^{(t)}\,\mathbf{W}_{ih} + \mathbf{b}_{ih} + \mathbf{h}^{(t-1)}\,\mathbf{W}_{hh} + \mathbf{b}_{hh}) $$

Обов'язкові параметри: input_size - розмірність входів ($\mathbf{x}$) і hidden_size - розмірність виходу ( = прихованого стану $\mathbf{h}$).

За замовчуванням розмірність входу (seq_len, batch_size, input_size), де seq_len - число RNN-комірок (входів). Індекс батча можна зробити першим за допомогою параметра batch_first. Утім тензор за замовчуванням виходить, якщо перед шаром RNN знаходиться шар Embedding з seq_len входами і з input_size = VEC_DIM розмірністю векторів.

Шар повертає кортеж з тензора всіх виходів мережі форми (seq_len, batch_size, hidden_size) і останнього прихованого стану (останній вихід) форми (num_layers, batch_size, hidden_size), де для одного шару num_layers = 1. За замовчуванням компоненти початкового прихованого стану нульові. Інакше їх можна передати другим аргументом:

X_dim  = 2           # розмірність входів            = dim(x)
H_dim  = 4           # розмірність прихованого стану = dim(h)
L      = 3           # кількість входів (комірок RNN-шару)
B      = 1           # кількість прикладів (batch_size)

rnn = nn.RNN(X_dim, H_dim)

X  = torch.zeros(L, B, X_dim)
H0 = torch.zeros(1, B, H_dim)     

Y, Hn = rnn(X, H0)                       # H0 не обов’язково, за замовчуванням і так 0

print(tuple(Y.shape), tuple(Hn.shape))   #  (3, 1, 4) (1, 1, 4)
print(Y[-1] == Hn[-1])                   #  tensor([[[True, True, True, True]]])

У шарі можна відразу задати стопку RNN-шарів (num_layers) Між шарами можна вставити режим випадкового забивання нулями виходів з ймовірністю dropout. Шар можна також перетворити на двонаправлений: bidirectional = True. У цьому випадку розмірність виходу буде (inputs, batch_size, 2*hidden_size), а розмірність початкового прихованого стану (2 * num_layers, batch_size, hidden_size).


LSTM

torch.nn.LSTM
… (input_size, hidden_size, num_layers=1, nonlinearity='tanh', bias=True,
… batch_first=False, dropout=0, bidirectional=False)
[doc]

Аналогічний RNN. Подробиці див. у NN_RNN.html. Крім прихованого стану $\mathbf{h}$ має пам'ять комірок $\mathbf{c}$, див. документацію.

rnn = nn.LSTM(10, 20, 2)
...
output, (hn, cn) = rnn(input, (h0, c0))

MultiheadAttention

nn.MultiheadAttention
… (embed_dim, num_heads, dropout=0.0, bias=True, add_bias_kv=False,
… add_zero_attn=False, kdim=None, vdim=None)

$$ \text{MultiHead}(\mathbf{Q},\mathbf{K}, \mathbf{V}) = \text{Concat}(\text{h}_1,\dots,\text{h}_H)\,\mathbf{W}^O, ~~~~~~~ \text{h}_i = \text{Attn}(\mathbf{Q}\,\mathbf{W}_i^Q,~ \mathbf{K}\,\mathbf{W}_i^K,~ \mathbf{V}\,\mathbf{W}_i^V) $$

Inputs: Q:(L,N,E), K,V:(S,N,E), де L - довжина послідовності target (для декодера),
N - розмір батча, E - розмірність ембедингу (embed_dim), S - довжина послідовності source.

Outputs: output: (L,N,E), weights: (N,L,S)

Розмірності матриць: $\mathbf{W}^Q:$ (E, E), $\mathbf{W}^K:$ (E, kdim), $\mathbf{W}^V:$ (E, vdim), де kdim,vdim задаються або дорівнюють E, але вони передаються у функцію linear, тому при множенні транспонуються.
Розмірність кожної голови дорівнює head_dim = E // num_heads.
Вихідна матриця $\mathbf{W}^O:$ це шар Linear(E, E, bias=bias).
Якщо bias є, він додається після множення на проєкційні матриці $\mathbf{W}^Q,\mathbf{W}^K,\mathbf{W}^V$ (до кожної свій).

Якщо ембединг входів однаковий, то проєкційні матриці упаковані в in_proj_weight форми (3*E,E), інакше це параметри: q_proj_weight, k_proj_weight, v_proj_weight. Зсув (якщо він є) це in_proj_bias форми (3*E,), а вихідний шар: out_proj.

Концептуально відбуваються такі обчислення (N - розмір батча, подробиці див. у NN_Attention.html):

q = linear(Q, q_proj_weight, in_proj_bias[0 : E])     # (L,N,E) @ (E, E).T    = (L,N,E)
k = linear(K, k_proj_weight, in_proj_bias[E : E*2])   # (S,N,E) @ (E, kdim).T = (S,N,kdim)
v = linear(V, v_proj_weight, in_proj_bias[E*2:])      # (S,N,E) @ (E, vdim).T = (S,N,vdim)

q = q.contiguous().view( L, N * num_heads, head_dim).transpose(0,1)
k = k.contiguous().view(-1, N * num_heads, head_dim).transpose(0,1)
v = v.contiguous().view(-1, N * num_heads, head_dim).transpose(0,1)

a_weights = torch.bmm(q, k.transpose(1,2))
a_weights = softmax(a_weights, dim=-1)

a = torch.bmm(a_weights, v)

a = a.transpose(0, 1).contiguous().view(L, N, E)
a = linear(a, out_proj_weight, out_proj_bias)

TransformerEncoderLayer

nn.TransformerEncoderLayer
… (d_model, nhead, dim_feedforward=2048, dropout=0.1, activation='relu')

Параметри: d_model – розмірність входу (вектора одного токена), nhead – число голів (дільник параметра d_model), dim_feedforward – розмірність повнозв'язної мережі.


TransformerEncoder

nn.TransformerEncoder
… (encoder_layer, num_layers, norm=None)

L, N, E = 10, 32, 512            # число слов, размер батча, размерность эмбединга

encoder_layer       = nn.TransformerEncoderLayer(d_model=E, nhead=8)
transformer_encoder = nn.TransformerEncoder     (encoder_layer, num_layers=6)

src = torch.rand(L, N, E)
out = transformer_encoder(src)   # out.shape == src.shape

Шари без параметрів


Лінеаризація тензора

nn.Flatten(start_dim=1, end_dim=-1)

При створенні з параметрами за замовчуванням, усі індекси крім першого (індекс прикладу) зіллються в один (для кожного прикладу вийде вектор). Шар параметрів не має.

X = torch.ones(2, 5,4,3)
Y = torch.nn.Flatten()(X)
print( tuple(Y.shape) )                   # (2, 60)


Косинус між векторами

nn.CosineSimilarity(dim=1, eps=1e-08)

$$ \frac{\mathbf{u}\mathbf{v}}{ \max( |\mathbf{u}|\,|\mathbf{v}|,~~\epsilon) } $$

Input1, Input2: (*, D, *), де D - розмірність вектора і форми однакові. Output: (*,*).
Нижче косинус обчислюється між рядками матриць:
u, v = torch.randn(100, 128), torch.randn(100, 128)
output = nn.CosineSimilarity(dim=1, eps=1e-6) (u, v)

Відстань між векторами

nn.PairwiseDistance(p=2.0, eps=1e-06, keepdim=False)

$$ \bigr(\sum_{i} |x_i|^p \Bigr)^{1/p} $$

Input1, Input2: (N, D), де D - розмірність вектора. Output: (N) або (N, 1), якщо keepdim = True.

Вимкнення нейронів

nn.Dropout(p=0.5, inplace=False)

У режимі тренування: m.train(), з ймовірністю "p" занулює кожен елемент матриці і ділить їх на 1-p.
У режимі оцінювання: m.eval() нічого не робить. Завдяки діленню на 1-p середнє значення за модулем елементів у режимі тренування буде таким же, як і в режимі оцінювання (у вхідного тензора).

m = nn.Dropout(p = 0.2)
                               #                     v                      v
print( m(torch.ones(10)) )     # [1.25, 1.25, 1.25, 0.0, 1.25, 1.25, 1.25, 0.0, 1.25, 1.25]

x = torch.randn(100, 100)

m.eval()                       # This is equivalent with self.train(False)
y = m(x)
print( (x==y).float().mean() ) # не змінюється

m.train() 
y =m(x)

print(x.abs().mean(),   (x*x < 1e-10).float().mean()  )   # 0.8025     0.
print(y.abs().mean(),   (y*y < 1e-10).float().mean()  )   # 0.8001     0.206

Нормалізація тензора

nn.BatchNorm1d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)

За батчем (перший індекс) обчислюються середні значення і дисперсії за кожною ознакою незалежно. Потім робиться нормалізація даних і афінний зсув з навчальними параметрами $\gamma,\,\beta$. $$ y = \frac{x - \mathrm{E}[x]}{\sqrt{\mathrm{Var}[x] + \epsilon}} \odot \text{weight} + \text{bias} $$ Вхід повинен бути тензором форми (N,F) або (N,F,L). Параметр num_features = F. Для тензора (N,F,L) усереднення відбувається для dim=0,2, тобто однакові середні за кожним "входом" останнього індексу [0...L-1].

У процесі тренування середні значення згладжуються ковзним середнім з параметром momentum (якщо None - то звичайне середнє). Ці середні запам'ятовуються і потім використовуються при тестуванні (навіть з одним батчем).

m =  nn.BatchNorm1d(2)

x = torch.randn(100000,2)
print( x.mean(dim=0), x.var(dim=0) )    # [-0.0005,  0.0067]      [1.0058, 0.9956]

x = 2 *  x + 3 
print( x.mean(dim=0), x.var(dim=0) )    # [ 3.0010,  2.9867]      [4.0231, 3.9824]

y = m(x)
with torch.no_grad():
    print(y.mean(dim=0), y.var(dim=0))  # [ 0.0000,  0.0000]      [1.0000, 1.0000]
    
    
for k, v in m.state_dict().items():     # weight             :(2,)
    print(f'{k:20s}: {tuple(v.shape)}') # bias               :(2,)
                                        # running_mean       :(2,)
                                        # running_var        :(2,)
                                        # num_batches_tracked:(  )

MaxPool2d

nn.MaxPool2d
(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)

AvgPool2d

nn.AvgPool2d
(kernel_size, stride=None, padding=0, ceil_mode=False, count_include_pad=True, divisor_override=None)

Активаційні функції


Сигмоїд: $\mathrm{Sigmoid}(x) = 1/(1+e^{-x})$

nn.Sigmoid() [doc]
F.sigmoid(x)
Tanh: $\mathrm{Tanh}(x) = (e^{x}-e^{-x})/(e^{x}+e^{-x})$

nn.Tanh() [doc]
F.tanh(x)
ReLU: $\mathrm{Sigmoid}(x) = \max(0,x)$

nn.ReLU(inplace=False) [doc]
F.relu(x, inplace=False)
ELU: $\mathrm{ELU}(x) = \max(0,x) + \min(0, \alpha\,(e^{x}-1))$

nn.ELU(alpha=1.0, inplace=False) [doc]
nn.elu(x, alpha=1.0, inplace=False)
Софтмакс: $\mathrm{Softmax}(x_i) = e^{x_i}/\sum_j e^{x_j}$

nn.Softmax(dim=None) [doc].
F.softmax(x, dim)

X = torch.tensor( [ [1.,2.,3] ] )
torch.nn.Softmax(dim=1)(X)              # tensor([[0.09, 0.24, 0.66]])


Логарифмічний софтмакс: $\mathrm{LogSoftmax}(x_i) = \log \bigr(e^{x_i}/\sum_j e^{x_j}\bigr)$

nn.LogSoftmax (dim=None) [doc].

Функції похибки


Середньоквадратична похибка

nn.MSELoss
… (size_average=None, reduce=None, reduction='mean')
[doc] $$ L(y,\hat{y}) = (y - \hat{y})^2 \left\{ \begin{array}{ll} .\mathrm{mean}() & \text{if reduction='mean'}\\ .\mathrm{sum}() & \text{if reduction='sum'} \end{array} \right. $$

За замовчуванням (reduction='mean') квадрати відхилень усереднюються за всіма прикладами батча і за всіма виходами (похибка це скаляр!).

loss = torch.nn.MSELoss()

y  = torch.rand(10, 2)                  # 10 прикладів, 2 виходи
yb = torch.rand(10, 2)

loss(y, yb) == ((y-yb)**2).mean()  


Бінарна крос-ентропія

nn.BCELoss
… (weight=None, size_average=None, reduce=None, reduction='mean')
[doc]

Передбачається, що ця похибка ставиться після сигмоїдної активації, тобто виходи моделі y і цільові значення yb знаходяться в діапазоні [0...1]. Зазвичай BCELoss використовується для мережі з одним виходом (задача для двох класів 0 і 1). Якщо класи перетинаються (тобто приклад може належати відразу до декількох класів), то число виходів дорівнює числу класів. За замовчуванням (reduction='mean') похибки усереднюються за всіма прикладами і всіма виходами:

loss = torch.nn.BCELoss()

y  = torch.rand(10, 2, 3)                  # 10 примеров, 2x3 выходов
yb = torch.rand(10, 2, 3)

print(    loss(y, yb) )
print(  -( yb*y.log() + (1-yb)*(1-y).log() ).mean()  )
Вектор weight з числом компонент рівних числу прикладів у батчі може по-різному зважувати різні приклади. Похибка для $i$-того прикладу ($y$ - вихід моделі, $\hat{y}$ - цільове значення): $$ L_i = -w_i\,\bigr[ \hat{y}_i\,\log y_i + (1-\hat{y}_i)\,\log (1-y_i)\bigr] $$


Крос-ентропія

nn.CrossEntropyLoss
… (weight=None,size_average=None,ignore_index=-100,reduce=None,reduction='mean')
[doc]

Застосовується до задачі класифікації непересічних $C$ класів. Мережа повинна мати $C$ виходів, тобто в похибку з моделі входить тензор $y_{i\alpha}$ форми $(N,C)$, де $N$ - розмір батча. Цільовими є цілі числа $\hat{y}_i \in [0...C-1]$ - номери класів кожного прикладу форми (N,). Похибка для одного $i$-того прикладу і $\hat{y}_i=c$ дорівнює:

$$ L(y, c) = -\,w_c\,\log\left( \frac{\exp {y_{ic}}}{ \sum_\alpha \exp{y_{i\alpha}}}\right). $$ Ваги $w_\alpha$ (за числом класів) можуть посилювати внесок окремих класів. Таким чином, CrossEntropyLoss поєднує в собі Softmax і на виході мережі його ставити не потрібно. $C$ виходів мережі $y_{i\alpha}$ перетворюються на "ймовірності" і максимізується логарифм ймовірності правильного класу $\hat{y}_i=c$ (знак мінус - похибка мінімальна). Підсумкова похибка дорівнює середньому $L(y, c)$ за всіма прикладами батча (якщо reduction='mean').

Вхідні дані у функцію похибки CE_loss(y, y_t)можуть мати форму y=(B,C):float, y_t=(B,):long або в n-вимірному випадку: y=(B,C,L1,...,Ln):float, y_t=(B,L1,...,Ln):long

B, C, L = 10, 5, 4                   # прикладів, класів, виходів
w = torch.rand(C)                    # ваги важливості класів

CE_loss = nn.CrossEntropyLoss(weight=w, ignore_index=1)   

y     = torch.randn(B, C, L)
yb    = torch.empty(B, L,     dtype=torch.long).random_(C)

loss  = CE_loss(y, yb)
print(loss)                          # похибка
Відтворимо, отримане значення:
y = -nn.Softmax(dim=1)(y).log()      # мінус логарифм від софтмаксу

loss, norm = 0, 0
for b in range(B):
    for l in range(L):
        c = yb[b,l]                  # правильний клас
        if c != 1:                   # ігноруємо клас 1
            loss += w[c]*y[b, c, l]  # помилка
            norm += w[c]             # нормування
print(loss/norm)

Логарифмічний максимум правдоподібності

nn.NLLLoss
… (weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean')
[doc]

Результат буде таким же, як і при CrossEntropyLoss, якщо останнім шаром стоїть LogSoftmax.


Оптимізатори

torch.optim.Adam(params, lr=0.001, betas=(0.9, 0.999), 
                 eps=1e-08, weight_decay=0, amsgrad=False)
torch.optim.Adagrad(params, lr=0.01, lr_decay=0, weight_decay=0, 
                    initial_accumulator_value=0, eps=1e-10)
torch.optim.Adadelta(params, lr=1.0, rho=0.9, eps=1e-06, weight_decay=0)
torch.optim.RMSprop(params, lr=0.01, alpha=0.99, weight_decay=0, momentum=0,
                    eps=1e-08, centered=False)
torch.optim.Rprop(params, lr=0.01, etas=(0.5, 1.2), step_sizes=(1e-06, 50))
torch.optim.SGD(params, lr, momentum=0, 
                dampening=0, weight_decay=0, nesterov=False)