6.4. Ленивая инициализация

До сих пор могло казаться, что нам удалось избежать ошибок при настройке наших сетей. В частности, мы совершали следующие неинтуитивные действия, которые, казалось бы, не должны были работать:

  • Мы определили архитектуры сетей, не указывая размерность входных данных.

  • Мы добавляли слои, не указывая выходную размерность предыдущего слоя.

  • Мы даже «инициализировали» эти параметры, не имея достаточной информации для определения того, сколько параметров должно быть в наших моделях.

Вас может удивить, что наш код вообще работает. В конце концов, фреймворк глубокого обучения не мог знать, какой будет размерность входных данных сети. Секрет в том, что фреймворк откладывает инициализацию, ожидая первого прохода данных через модель, чтобы динамически определить размеры каждого слоя.

В дальнейшем, при работе с свёрточными нейронными сетями, этот подход станет ещё более удобным, поскольку размерность входа (например, разрешение изображения) влияет на размерность каждого последующего слоя. Следовательно, возможность задавать параметры без необходимости знать значение размерности на этапе написания кода, может значительно упростить задачу определения и последующего изменения наших моделей. Далее мы подробнее рассмотрим механику инициализации.

import torch
from torch import nn
from d2l import torch as d2l

Для начала создадим экземпляр многослойного перцептрона (MLP).

net = nn.Sequential(nn.LazyLinear(256), nn.ReLU(), nn.LazyLinear(10))

На этом этапе сеть не может знать размерности весов входного слоя, поскольку размерность входа остаётся неизвестной.

Следовательно, фреймворк ещё не инициализировал никакие параметры. Подтвердим это, попытавшись получить доступ к параметрам ниже.

net[0].weight

Теперь давайте пропустим данные через сеть, чтобы фреймворк наконец инициализировал параметры.

X = torch.rand(2, 20)
net(X)

net[0].weight.shape

Как только становится известна размерность входных данных — 20, — фреймворк может определить форму весовой матрицы первого слоя, подставив значение 20. Определив форму первого слоя, фреймворк переходит ко второму слою и так далее по вычислительному графу, пока все формы не станут известны. Обратите внимание, что в данном случае только первый слой требует ленивой инициализации, однако фреймворк инициализирует параметры последовательно. Как только формы всех параметров становятся известны, фреймворк наконец может инициализировать параметры.

Следующий метод пропускает фиктивные входные данные через сеть для пробного запуска, чтобы определить формы всех параметров, а затем инициализирует их. Этот метод будет использоваться позже, когда стандартная случайная инициализация не требуется.

@d2l.add_to_class(d2l.Module)  #@save
def apply_init(self, inputs, init=None):
    self.forward(*inputs)
    if init is not None:
        self.net.apply(init)

6.4.1. Итоги

Ленивая инициализация удобна тем, что позволяет фреймворку автоматически выводить формы параметров, упрощает изменение архитектур и устраняет один из распространённых источников ошибок. Чтобы фреймворк наконец инициализировал параметры, можно пропустить данные через модель.

6.4.2. Упражнения

  1. Что произойдет, если вы укажете размерности входных данных для первого слоя, но не для последующих? Произойдет ли немедленная инициализация?

  2. Что произойдет, если указать несовместимые размеры?

  3. Что нужно сделать, если входные данные имеют различную размерность? Подсказка: посмотрите на привязку параметров.

Обсуждения