PyTorch: ручная настройка весовых параметров с помощью массива numpy для GRU / LSTM - PullRequest
0 голосов
/ 23 октября 2018

Я пытаюсь заполнить GRU / LSTM вручную определенными параметрами в pytorch.

У меня есть несколько массивов для параметров с формами, как определено в их документации (https://pytorch.org/docs/stable/nn.html#torch.nn.GRU).

Этокажется, работает, но я не уверен, верны ли возвращенные значения.

Это правильный способ заполнить GRU / LSTM с numpy параметрами?

gru = nn.GRU(input_size, hidden_size, num_layers,
              bias=True, batch_first=False, dropout=dropout, bidirectional=bidirectional)

def set_nn_wih(layer, parameter_name, w, l0=True):
    param = getattr(layer, parameter_name)
    if l0:
        for i in range(3*hidden_size):
            param.data[i] = w[i*input_size:(i+1)*input_size]
    else:
        for i in range(3*hidden_size):
            param.data[i] = w[i*num_directions*hidden_size:(i+1)*num_directions*hidden_size]

def set_nn_whh(layer, parameter_name, w):
    param = getattr(layer, parameter_name)
    for i in range(3*hidden_size):
        param.data[i] = w[i*hidden_size:(i+1)*hidden_size]

l0=True

for i in range(num_directions):
    for j in range(num_layers):
        if j == 0:
            wih = w0[i, :, :3*input_size]
            whh = w0[i, :, 3*input_size:]  # check
            l0=True
        else:
            wih = w[j-1, i, :, :num_directions*3*hidden_size]
            whh = w[j-1, i, :, num_directions*3*hidden_size:]
            l0=False

        if i == 0:
            set_nn_wih(
                gru, "weight_ih_l{}".format(j), torch.from_numpy(wih.flatten()),l0)
            set_nn_whh(
                gru, "weight_hh_l{}".format(j), torch.from_numpy(whh.flatten()))
        else:
            set_nn_wih(
                gru, "weight_ih_l{}_reverse".format(j), torch.from_numpy(wih.flatten()),l0)
            set_nn_whh(
                gru, "weight_hh_l{}_reverse".format(j), torch.from_numpy(whh.flatten()))

y, hn = gru(x_t, h_t)

numpy массивы определеныследующим образом:

rng = np.random.RandomState(313)
w0 = rng.randn(num_directions, hidden_size, 3*(input_size +
               hidden_size)).astype(np.float32)
w = rng.randn(max(1, num_layers-1), num_directions, hidden_size,
              3*(num_directions*hidden_size + hidden_size)).astype(np.float32)

1 Ответ

0 голосов
/ 07 ноября 2018

Это хороший вопрос, и вы уже даете достойный ответ.Тем не менее, он заново изобретает колесо - есть очень элегантная внутренняя процедура Pytorch, которая позволит вам делать то же самое без особых усилий - и та, которая применима для любой сети.

Основной концепцией здесь является PyTorch state_dict.Словарь состояний фактически содержит parameters, организованный древовидной структурой, определяемой отношением nn.Modules и его подмодулей и т. Д.

Краткий ответ

Если вы хотите толькокод для загрузки значения в тензор, используя state_dict, затем попробуйте эту строку (где dict содержит действительный state_dict):

`model.load_state_dict(dict, strict=False)`

, где strict=False имеет решающее значение, если вы хотитезагрузить только некоторые значения параметров .

Длинный ответ - включая введение в PyTorch state_dict

Вот пример того, как диктат состояния выглядит для GRU (Я выбрал input_size = hidden_size = 2, чтобы можно было распечатать все состояние dict):

rnn = torch.nn.GRU(2, 2, 1)
rnn.state_dict()
# Out[10]: 
#     OrderedDict([('weight_ih_l0', tensor([[-0.0023, -0.0460],
#                         [ 0.3373,  0.0070],
#                         [ 0.0745, -0.5345],
#                         [ 0.5347, -0.2373],
#                         [-0.2217, -0.2824],
#                         [-0.2983,  0.4771]])),
#                 ('weight_hh_l0', tensor([[-0.2837, -0.0571],
#                         [-0.1820,  0.6963],
#                         [ 0.4978, -0.6342],
#                         [ 0.0366,  0.2156],
#                         [ 0.5009,  0.4382],
#                         [-0.7012, -0.5157]])),
#                 ('bias_ih_l0',
#                 tensor([-0.2158, -0.6643, -0.3505, -0.0959, -0.5332, -0.6209])),
#                 ('bias_hh_l0',
#                 tensor([-0.1845,  0.4075, -0.1721, -0.4893, -0.2427,  0.3973]))])

Так что state_dict все параметры сети.Если мы «вложили» nn.Modules, мы получим дерево, представленное именами параметров:

class MLP(torch.nn.Module):      
    def __init__(self):
        torch.nn.Module.__init__(self)
        self.lin_a = torch.nn.Linear(2, 2)
        self.lin_b = torch.nn.Linear(2, 2)


mlp = MLP()
mlp.state_dict()
#    Out[23]: 
#        OrderedDict([('lin_a.weight', tensor([[-0.2914,  0.0791],
#                            [-0.1167,  0.6591]])),
#                    ('lin_a.bias', tensor([-0.2745, -0.1614])),
#                    ('lin_b.weight', tensor([[-0.4634, -0.2649],
#                            [ 0.4552,  0.3812]])),
#                    ('lin_b.bias', tensor([ 0.0273, -0.1283]))])


class NestedMLP(torch.nn.Module):
    def __init__(self):
        torch.nn.Module.__init__(self)
        self.mlp_a = MLP()
        self.mlp_b = MLP()


n_mlp = NestedMLP()
n_mlp.state_dict()
#   Out[26]: 
#        OrderedDict([('mlp_a.lin_a.weight', tensor([[ 0.2543,  0.3412],
#                            [-0.1984, -0.3235]])),
#                    ('mlp_a.lin_a.bias', tensor([ 0.2480, -0.0631])),
#                    ('mlp_a.lin_b.weight', tensor([[-0.4575, -0.6072],
#                            [-0.0100,  0.5887]])),
#                    ('mlp_a.lin_b.bias', tensor([-0.3116,  0.5603])),
#                    ('mlp_b.lin_a.weight', tensor([[ 0.3722,  0.6940],
#                            [-0.5120,  0.5414]])),
#                    ('mlp_b.lin_a.bias', tensor([0.3604, 0.0316])),
#                    ('mlp_b.lin_b.weight', tensor([[-0.5571,  0.0830],
#                            [ 0.5230, -0.1020]])),
#                    ('mlp_b.lin_b.bias', tensor([ 0.2156, -0.2930]))])

Итак, что делать, если вы хотите не извлекать dict состояния, а изменять его - и тем самым сетьпараметры?Использовать nn.Module.load_state_dict(state_dict, strict=True) ( ссылка на документы ). Этот метод позволяет загрузить весь state_dict с произвольными значениями в конкретную модель того же вида , пока ключи (то естьимена параметров) являются правильными, а значения (то есть параметры) torch.tensors правильной формы.Если для strict kwarg установлено значение True (по умолчанию), то загружаемый вами дикт должен точно соответствовать исходному состоянию, кроме значений параметров.То есть для каждого параметра должно быть одно новое значение.

Для приведенного выше примера GRU нам нужен тензор правильного размера (и правильное устройство, кстати) для каждого из 'weight_ih_l0', 'weight_hh_l0', 'bias_ih_l0', 'bias_hh_l0'.Так как иногда мы хотим загружать некоторые значения (как я думаю, вы хотите сделать), мы можем установить strict kwarg на False - и тогда мы можем загружать только частичные состояния, например:тот, который содержит только значения параметров для 'weight_ih_l0'.

В качестве практического совета, я бы просто создал модель, в которую вы хотите загрузить значения, а затем напечатал бы dict состояния (или, по крайней мере, списокключи и соответствующие размеры тензора)

print([k, v.shape for k, v in model.state_dict().items()])

Это говорит о том, какое именно имя параметра вы хотите изменить.Затем вы просто создаете dict состояния с соответствующим именем параметра и тензором и загружаете его:

from dollections import OrderedDict
new_state_dict = OrderedDict({'tensor_name_retrieved_from_original_dict': new_tensor_value})
model.load_state_dict(new_state_dict, strict=False)
...