nn.Sequential и параметры модели

Учимся собирать сети короче через nn.Sequential и заглядывать в их параметры.

nn.Sequential — контейнер, который прогоняет вход через список слоёв по очереди, избавляя от написания класса.

nn.Sequential: сеть в одну строку

Если сеть — это просто цепочка слоёв «один за другим», писать целый класс необязательно. nn.Sequential сам прогонит данные через слои в порядке их перечисления:

import torch
import torch.nn as nn

model = nn.Sequential(
    nn.Linear(784, 128),
    nn.ReLU(),
    nn.Linear(128, 10),
)

x = torch.randn(16, 784)
out = model(x)
print(out.shape)   # torch.Size([16, 10])

Это полный аналог класса Net из прошлых уроков, но компактнее. Sequential хорош для простых линейных пайплайнов. Как только нужна логика (ветвление, повторное использование выхода, несколько входов) — возвращайтесь к классу с forward, там вы управляете потоком данных полностью.

Параметры модели

Все обучаемые веса модели доступны через model.parameters() — это итератор по тензорам. Именно его передают оптимизатору, чтобы он знал, какие тензоры двигать:

for p in model.parameters():
    print(p.shape, p.requires_grad)
# torch.Size([128, 784]) True   <- W первого слоя
# torch.Size([128])      True   <- bias первого слоя
# torch.Size([10, 128])  True   <- W второго слоя
# torch.Size([10])       True   <- bias второго слоя

Видно, что Sequential сам собрал параметры всех слоёв. Это работает потому, что слои зарегистрированы как подмодули — та же магия nn.Module, что и при присваивании в self.

named_parameters: с именами

Чтобы понять, какой тензор за что отвечает, удобнее named_parameters() — он отдаёт пары «имя, тензор»:

for name, p in model.named_parameters():
    print(name, tuple(p.shape))
# 0.weight (128, 784)
# 0.bias   (128,)
# 2.weight (10, 128)
# 2.bias   (10,)

Числа в именах — это индексы слоёв в Sequential (слой 1 — это ReLU, у неё параметров нет, поэтому она не встречается).

Сколько в модели параметров

Размер модели принято измерять числом обучаемых параметров. Считают так: складывают numel() (число элементов) всех параметров. Идею подсчёта легко показать на чистом Python — этот код запускается:

# имитируем формы параметров сети 784 -> 128 -> 10
shapes = [(128, 784), (128,), (10, 128), (10,)]

def numel(shape):
    total = 1
    for d in shape:
        total *= d
    return total

params = sum(numel(s) for s in shapes)
print("всего параметров:", params)

Вывод:

всего параметров: 101770

В реальном PyTorch это пишут одной строкой: sum(p.numel() for p in model.parameters()). Так вы быстро оцениваете «вес» модели — от тысяч у учебных сетей до миллиардов у больших языковых моделей.

Итог

  • nn.Sequential прогоняет вход через слои по очереди — компактная замена классу для простых сетей.
  • Для ветвлений и сложной логики нужен класс с собственным forward.
  • model.parameters() отдаёт все обучаемые веса — их передают оптимизатору.
  • Размер модели = сумма p.numel() по всем параметрам.
Проверьте себя
1. Когда удобнее писать класс с forward, а не nn.Sequential?
AВсегда, Sequential не нужен
BКогда нужна логика: ветвления, повторное использование выходов, несколько входов
CТолько для свёрточных сетей
DКогда в сети больше трёх слоёв
2. Что возвращает model.parameters() и зачем оно нужно?
AГиперпараметры обучения
BИтератор по обучаемым тензорам-весам, который передают оптимизатору
CАрхитектуру модели в виде текста
DГрадиенты всех слоёв
3. Как оценить число обучаемых параметров модели?
Alen(model.parameters())
Bsum(p.numel() for p in model.parameters())
Cmodel.size()
DЧисло слоёв умножить на размер батча
Поддержать проект